Docs
OpenAPI specification
OpenAPI specification
Raw YAML for the TenantX HTTP API. Import into Swagger UI, Redoc, or your API client.
Large file — use your editor or an OpenAPI viewer for the best experience.
openapi: 3.1.0
info:
title: Tenantx API
version: "1.0"
description: |
Tenantx is a multi-tenant B2B SaaS starter kit (Laravel 12 + React 18).
## Authentication
All protected endpoints require a **Bearer token** in the `Authorization` header.
Obtain a token via `POST /api/auth/login`.
## Tenancy model
```
Platform Admin (global subscription.admin)
└── Organization (billing + permission boundary)
└── Workspace (workspace_branding row; legacy name)
└── User
```
## Base URL
Default local: `http://localhost:8000/api`
contact:
name: Tenantx
license:
name: MIT
servers:
- url: http://localhost:8000/api
description: Local development
tags:
- name: Auth
description: Authentication and account management
- name: Password Reset
description: Self-service password reset flow
- name: 2FA
description: TOTP two-factor authentication
- name: Organizations
description: Organization CRUD (tenant-scoped)
- name: Users
description: User management within an organization
- name: Workspaces
description: Workspace (workspace_branding) management
- name: Invitations
description: Team invitation flow
- name: Permissions
description: Spatie permission + role management
- name: Roles
description: Role management
- name: Help Center
description: Help center articles and categories
- name: Notifications
description: In-app notification management
- name: Activity Logs
description: Audit activity log (tenantx_logs schema, PostgreSQL only)
- name: Dashboard
description: Dashboard statistics
- name: Subscription
description: Organization subscription, plans, usage
- name: Search
description: Global search within workspace
- name: API Tokens
description: Personal API token management
- name: System Settings
description: Organization-scoped system settings
- name: Platform Admin
description: Platform admin surface (requires subscription.admin)
- name: Public
description: Public endpoints — no authentication required
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
Profile:
type: object
properties:
id: { type: string, format: uuid }
email: { type: string, format: email }
full_name: { type: string, nullable: true }
role: { type: string, nullable: true }
phone: { type: string, nullable: true }
avatar_url: { type: string, nullable: true }
is_active: { type: boolean }
organization_id: { type: string, format: uuid, nullable: true }
default_workspace_id:
{
type: string,
format: uuid,
nullable: true,
description: "User's default workspace UUID",
}
workspaces_access_all:
{ type: boolean, description: "Can switch workspace context" }
Organization:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
slug: { type: string }
settings: { type: object }
created_at: { type: string, format: date-time }
updated_at: { type: string, format: date-time }
Workspace:
type: object
description: "Workspace branding row (stored in workspace_branding table)"
properties:
id: { type: string, format: uuid }
organization_id: { type: string, format: uuid }
name: { type: string, description: "Workspace display name" }
email: { type: string, nullable: true }
phone: { type: string, nullable: true }
created_at: { type: string, format: date-time }
Invitation:
type: object
properties:
id: { type: string, format: uuid }
email: { type: string, format: email }
role: { type: string, example: member }
invited_by_user_id: { type: string, format: uuid, nullable: true }
expires_at: { type: string, format: date-time }
created_at: { type: string, format: date-time }
Notification:
type: object
properties:
id: { type: string, format: uuid }
title: { type: string }
body: { type: string, nullable: true }
type: { type: string }
is_read: { type: boolean }
url: { type: string, nullable: true }
created_at: { type: string, format: date-time }
Permission:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
guard_name: { type: string }
organization_id: { type: string, format: uuid, nullable: true }
Role:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
guard_name: { type: string }
organization_id: { type: string, format: uuid, nullable: true }
SubscriptionPlan:
type: object
properties:
id: { type: string, format: uuid }
name: { type: string }
slug: { type: string }
price: { type: number }
currency: { type: string }
features: { type: array, items: { type: string } }
HelpCenterArticle:
type: object
properties:
id: { type: string, format: uuid }
title: { type: string }
slug: { type: string }
body: { type: string }
category_id: { type: string, format: uuid }
language: { type: string, example: en }
status: { type: string, enum: [draft, published, archived] }
is_global: { type: boolean }
ApiToken:
type: object
properties:
id: { type: integer }
name: { type: string }
last_used_at: { type: string, format: date-time, nullable: true }
created_at: { type: string, format: date-time }
Error:
type: object
properties:
message: { type: string }
errors:
{
type: object,
additionalProperties: { type: array, items: { type: string } },
}
security:
- BearerAuth: []
paths:
# ──────────────────────────────────────────────────────────────────────────
# Public — no auth
# ──────────────────────────────────────────────────────────────────────────
/health:
get:
tags: [Public]
summary: Health check
security: []
responses:
"200":
description: Service is healthy
content:
application/json:
schema:
type: object
properties:
status: { type: string, example: ok }
service: { type: string, example: Tenantx API }
/auth/login:
post:
tags: [Auth]
summary: Sign in
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email, password]
properties:
email: { type: string, format: email }
password: { type: string }
responses:
"200":
description: Authenticated
content:
application/json:
schema:
type: object
properties:
token: { type: string }
user: { type: object }
profile: { $ref: "#/components/schemas/Profile" }
"422":
description: Invalid credentials or account locked
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
/auth/register:
post:
tags: [Auth]
summary: Self-serve registration (creates org + first admin user)
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
[
email,
password,
password_confirmation,
full_name,
organization_name,
]
properties:
email: { type: string, format: email }
password: { type: string, minLength: 8 }
password_confirmation: { type: string }
full_name: { type: string }
organization_name: { type: string }
responses:
"201": { description: Account created, token returned }
"422": { description: Validation error }
/auth/forgot-password:
post:
tags: [Password Reset]
summary: Request a password reset link (always returns 200 to prevent enumeration)
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email]
properties:
email: { type: string, format: email }
responses:
"200":
description: Reset link sent (if email exists)
content:
application/json:
schema:
type: object
properties:
message: { type: string }
/auth/reset-password:
post:
tags: [Password Reset]
summary: Reset password using token from email link (token valid 60 min)
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email, token, password, password_confirmation]
properties:
email: { type: string, format: email }
token: { type: string }
password: { type: string, minLength: 8 }
password_confirmation: { type: string }
responses:
"200": { description: Password reset — all Sanctum tokens revoked }
"422": { description: Invalid / expired token }
/auth/two-factor/verify:
post:
tags: [2FA]
summary: Verify TOTP code during login challenge
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [login_token, code]
properties:
login_token: { type: string }
code: { type: string, minLength: 6, maxLength: 8 }
responses:
"200": { description: Authenticated, token returned }
"422": { description: Invalid code }
/invitations/accept:
get:
tags: [Invitations]
summary: Get invitation metadata (public — no auth)
security: []
parameters:
- in: query
name: token
required: true
schema: { type: string }
- in: query
name: email
required: true
schema: { type: string, format: email }
responses:
"200":
description: Invitation details
content:
application/json:
schema:
type: object
properties:
email: { type: string }
role: { type: string }
organization:
{
type: object,
properties:
{ id: { type: string }, name: { type: string } },
}
invited_by: { type: string, nullable: true }
expires_at: { type: string, format: date-time }
"422": { description: Invalid or expired invitation }
post:
tags: [Invitations]
summary: Accept invitation — creates account if needed, returns auth token
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [token, email, password, password_confirmation]
properties:
token: { type: string }
email: { type: string, format: email }
password: { type: string, minLength: 8 }
password_confirmation: { type: string }
full_name: { type: string }
responses:
"201":
description: Account created/joined, auth token returned
content:
application/json:
schema:
type: object
properties:
message: { type: string }
token: { type: string }
"422": { description: Invalid or expired invitation }
/subscription/plans:
get:
tags: [Subscription]
summary: List public subscription plans
security: []
responses:
"200": { description: Plans list }
# ──────────────────────────────────────────────────────────────────────────
# Authenticated — auth:sanctum + organization
# ──────────────────────────────────────────────────────────────────────────
/auth/logout:
post:
tags: [Auth]
summary: Revoke current token
responses:
"200": { description: Logged out }
/auth/profile:
get:
tags: [Auth]
summary: Get current user profile
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/Profile" }
put:
tags: [Auth]
summary: Update current user profile
requestBody:
content:
application/json:
schema:
type: object
properties:
full_name: { type: string }
phone: { type: string }
responses:
"200": { description: Updated profile }
/auth/change-password:
post:
tags: [Auth]
summary: Change own password (throttle 5/min)
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
[current_password, new_password, new_password_confirmation]
properties:
current_password: { type: string }
new_password: { type: string, minLength: 8 }
new_password_confirmation: { type: string }
responses:
"200": { description: Password changed }
"422": { description: Wrong current password }
/auth/two-factor/status:
get:
tags: [2FA]
summary: Get 2FA status for current user
responses:
"200":
content:
application/json:
schema:
type: object
properties:
enabled: { type: boolean }
confirmed: { type: boolean }
/auth/two-factor/setup:
post:
tags: [2FA]
summary: Generate TOTP secret and QR code URI
responses:
"200":
content:
application/json:
schema:
type: object
properties:
secret: { type: string }
qr_code:
{
type: string,
description: "otpauth:// URI for QR code generation",
}
/auth/two-factor/confirm:
post:
tags: [2FA]
summary: Confirm 2FA setup with a valid TOTP code
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [code]
properties:
code: { type: string, minLength: 6, maxLength: 8 }
responses:
"200": { description: 2FA enabled }
"422": { description: Invalid code }
/auth/two-factor/disable:
post:
tags: [2FA]
summary: Disable 2FA
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [password]
properties:
password: { type: string }
responses:
"200": { description: 2FA disabled }
/organizations:
get:
tags: [Organizations]
summary: List organizations accessible to current user
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Organization" }
post:
tags: [Organizations]
summary: Create a new organization (throttle 20/min)
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name, slug]
properties:
name: { type: string }
slug: { type: string }
responses:
"201":
content:
application/json:
schema: { $ref: "#/components/schemas/Organization" }
/organizations/{id}:
get:
tags: [Organizations]
summary: Get organization by ID
parameters:
- {
name: id,
in: path,
required: true,
schema: { type: string, format: uuid },
}
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/Organization" }
put:
tags: [Organizations]
summary: Update organization
parameters:
- {
name: id,
in: path,
required: true,
schema: { type: string, format: uuid },
}
requestBody:
content:
application/json:
schema:
type: object
properties:
name: { type: string }
settings: { type: object }
responses:
"200": { description: Updated }
delete:
tags: [Organizations]
summary: Delete organization
parameters:
- {
name: id,
in: path,
required: true,
schema: { type: string, format: uuid },
}
responses:
"200": { description: Deleted }
/users:
get:
tags: [Users]
summary: List users in current organization
parameters:
- { name: search, in: query, schema: { type: string } }
- { name: role, in: query, schema: { type: string } }
- { name: is_active, in: query, schema: { type: boolean } }
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Profile" }
post:
tags: [Users]
summary: Create a user in this organization
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email, password, full_name, role]
properties:
email: { type: string, format: email }
password: { type: string, minLength: 8 }
full_name: { type: string }
role: { type: string }
phone: { type: string }
responses:
"201": { description: User created }
/users/{id}:
get:
tags: [Users]
summary: Get user by ID
parameters:
- {
name: id,
in: path,
required: true,
schema: { type: string, format: uuid },
}
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/Profile" }
put:
tags: [Users]
summary: Update user
parameters:
- {
name: id,
in: path,
required: true,
schema: { type: string, format: uuid },
}
requestBody:
content:
application/json:
schema:
type: object
properties:
full_name: { type: string }
role: { type: string }
is_active: { type: boolean }
responses:
"200": { description: Updated }
delete:
tags: [Users]
summary: Delete user
parameters:
- {
name: id,
in: path,
required: true,
schema: { type: string, format: uuid },
}
responses:
"200": { description: Deleted }
/invitations:
get:
tags: [Invitations]
summary: List pending invitations for the current organization
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Invitation" }
post:
tags: [Invitations]
summary: Send a team invitation by email
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email]
properties:
email: { type: string, format: email }
role: { type: string, default: member }
responses:
"201":
content:
application/json:
schema: { $ref: "#/components/schemas/Invitation" }
"422": { description: Email already in org or invalid }
/invitations/{id}:
delete:
tags: [Invitations]
summary: Cancel a pending invitation
parameters:
- {
name: id,
in: path,
required: true,
schema: { type: string, format: uuid },
}
responses:
"200": { description: Invitation cancelled }
"404": { description: Not found }
/workspaces:
get:
tags: [Workspaces]
summary: List workspaces for current organization
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Workspace" }
post:
tags: [Workspaces]
summary: Create a workspace
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name: { type: string }
email: { type: string, format: email }
phone: { type: string }
responses:
"201": { description: Workspace created }
/workspaces/{workspace}:
get:
tags: [Workspaces]
summary: Get workspace by ID
parameters:
- {
name: workspace,
in: path,
required: true,
schema: { type: string, format: uuid },
}
responses:
"200":
content:
application/json:
schema: { $ref: "#/components/schemas/Workspace" }
put:
tags: [Workspaces]
summary: Update workspace
parameters:
- {
name: workspace,
in: path,
required: true,
schema: { type: string, format: uuid },
}
requestBody:
content:
application/json:
schema:
type: object
properties:
name: { type: string }
email: { type: string }
responses:
"200": { description: Updated }
delete:
tags: [Workspaces]
summary: Delete workspace (requires multi_workspace feature)
parameters:
- {
name: workspace,
in: path,
required: true,
schema: { type: string, format: uuid },
}
responses:
"200": { description: Deleted }
/permissions/user:
get:
tags: [Permissions]
summary: Get current user's permissions (used by frontend useUserPermissions hook)
responses:
"200":
content:
application/json:
schema:
type: object
properties:
permissions:
type: array
items: { type: string }
/permissions:
get:
tags: [Permissions]
summary: List all permissions for current organization
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Permission" }
/roles:
get:
tags: [Roles]
summary: List roles for current organization
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Role" }
post:
tags: [Roles]
summary: Create a role
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name: { type: string }
responses:
"201": { description: Role created }
/notifications:
get:
tags: [Notifications]
summary: List notifications for current user
parameters:
- { name: page, in: query, schema: { type: integer } }
- { name: per_page, in: query, schema: { type: integer } }
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/Notification" }
/notifications/unread-count:
get:
tags: [Notifications]
summary: Get unread notification count
responses:
"200":
content:
application/json:
schema:
type: object
properties:
count: { type: integer }
/notifications/read-all:
post:
tags: [Notifications]
summary: Mark all notifications as read
responses:
"200": { description: All marked read }
/activity-logs:
get:
tags: [Activity Logs]
summary: List activity logs for current workspace (PostgreSQL only; requires workspace context)
parameters:
- {
name: workspace_id,
in: query,
required: true,
schema: { type: string, format: uuid },
description: "Workspace UUID (legacy param name)",
}
- { name: page, in: query, schema: { type: integer } }
- { name: per_page, in: query, schema: { type: integer } }
- { name: log_name, in: query, schema: { type: string } }
- { name: event, in: query, schema: { type: string } }
responses:
"200": { description: Paginated activity logs }
/dashboard/stats:
get:
tags: [Dashboard]
summary: Workspace-scoped dashboard statistics
parameters:
- {
name: workspace_id,
in: query,
required: true,
schema: { type: string, format: uuid },
}
responses:
"200": { description: Stats object }
/organization-dashboard/overview:
get:
tags: [Dashboard]
summary: Organization-wide dashboard overview (total workspaces, users, recent activity)
responses:
"200":
content:
application/json:
schema:
type: object
properties:
kernel: { type: string, example: tenantx }
summary: { type: object }
workspaces: { type: array }
/search:
get:
tags: [Search]
summary: Search users, organizations, workspaces, and help center articles
parameters:
- { name: q, in: query, required: true, schema: { type: string } }
- {
name: workspace_id,
in: query,
schema: { type: string, format: uuid },
}
responses:
"200": { description: Search results grouped by type }
/api-tokens:
get:
tags: [API Tokens]
summary: List personal API tokens for current user
responses:
"200":
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/ApiToken" }
post:
tags: [API Tokens]
summary: Create a new personal API token
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name]
properties:
name: { type: string }
responses:
"201":
content:
application/json:
schema:
type: object
properties:
id: { type: integer }
name: { type: string }
plain_token:
{ type: string, description: "Shown once — store securely" }
created_at: { type: string, format: date-time }
/api-tokens/{token}:
delete:
tags: [API Tokens]
summary: Revoke a personal API token
parameters:
- { name: token, in: path, required: true, schema: { type: integer } }
responses:
"200": { description: Token revoked }
/organization-system-settings:
get:
tags: [System Settings]
summary: Get organization system settings JSON blob
parameters:
- {
name: workspace_id,
in: query,
schema: { type: string, format: uuid },
}
responses:
"200":
content:
application/json:
schema:
type: object
properties:
settings: { type: object }
put:
tags: [System Settings]
summary: Update organization system settings
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
settings: { type: object }
responses:
"200": { description: Settings saved }
/subscription/status:
get:
tags: [Subscription]
summary: Get current organization subscription status
responses:
"200": { description: Subscription status }
/subscription/usage:
get:
tags: [Subscription]
summary: Get subscription usage (users, workspaces vs plan limits)
responses:
"200": { description: Usage object }
/subscription/features:
get:
tags: [Subscription]
summary: Get features available on current plan
responses:
"200": { description: Feature map }
# ──────────────────────────────────────────────────────────────────────────
# Platform Admin — auth:sanctum + platform.admin (subscription.admin required)
# ──────────────────────────────────────────────────────────────────────────
/platform/dashboard:
get:
tags: [Platform Admin]
summary: Platform admin dashboard overview
responses:
"200": { description: Platform metrics }
"403": { description: Not a platform admin }
/platform/organizations:
get:
tags: [Platform Admin]
summary: List all organizations
responses:
"200": { description: Organizations list }
post:
tags: [Platform Admin]
summary: Create an organization as platform admin
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [name, slug]
properties:
name: { type: string }
slug: { type: string }
responses:
"201": { description: Organization created }
/platform/organizations/{organizationId}/activate:
post:
tags: [Platform Admin]
summary: Activate an organization subscription
parameters:
- {
name: organizationId,
in: path,
required: true,
schema: { type: string, format: uuid },
}
responses:
"200": { description: Activated }
/platform/organizations/{organizationId}/suspend:
post:
tags: [Platform Admin]
summary: Suspend an organization subscription
parameters:
- {
name: organizationId,
in: path,
required: true,
schema: { type: string, format: uuid },
}
responses:
"200": { description: Suspended }
/platform/plans:
get:
tags: [Platform Admin]
summary: List subscription plans
responses:
"200": { description: Plans list }
post:
tags: [Platform Admin]
summary: Create a subscription plan
responses:
"201": { description: Plan created }
/platform/login-audit:
get:
tags: [Platform Admin]
summary: List login audit records
responses:
"200": { description: Login audit records }
/platform/maintenance/status:
get:
tags: [Platform Admin]
summary: Get maintenance mode status
responses:
"200": { description: Maintenance status }
/platform/maintenance/enable:
post:
tags: [Platform Admin]
summary: Enable maintenance mode
responses:
"200": { description: Maintenance enabled }
/platform/maintenance/disable:
post:
tags: [Platform Admin]
summary: Disable maintenance mode
responses:
"200": { description: Maintenance disabled }
/platform/users:
get:
tags: [Platform Admin]
summary: List all platform users
responses:
"200": { description: Users list }
post:
tags: [Platform Admin]
summary: Create a platform user
responses:
"201": { description: User created }