Skip to content

Central API v1

Base path: /api/central/v1

Auth: Sanctum bearer token, guard central-api.

Authorization: Spatie permissions + policies. The superadmin role bypasses all gates.

Response envelope on every endpoint:

json
{ "status": "success" | "error", "message": "...", "data": { } | [ ] | null, "meta": { } | null }

Paginated list endpoints populate meta (current_page, last_page, per_page, total, next_page_url, prev_page_url); single-resource endpoints set meta: null.

Auth & profile

MethodPathNotes
POST/auth/loginThrottled (throttle:auth-login, 5/min by email or IP)
POST/auth/forgot-password
POST/auth/reset-passwordBody: email, token, password, password_confirmation
GET/meCurrent user + roles/permissions
POST/meUpdate profile
POST/me/change-password
POST/me/logoutRevokes tokens
GET/dashboardPlatform stats — permission dashboard.view; see Dashboard payload

Platform

Tenants

MethodPathPermissionNotes
GET/tenantstenants.list
POST/tenantstenants.createProvisions default modules — see tenant provisioning
GET/tenants/{tenant}tenants.readIncludes installed_modules when loaded
PUT/PATCH/tenants/{tenant}tenants.update
DELETE/tenants/{tenant}tenants.deleteSoft delete
POST/tenants/{tenant}/restoretenants.restoreAlso restores the tenant's soft-deleted users and domains
DELETE/tenants/{tenant}/forcetenants.force.deleteForce-deletes users, domains, module subscriptions, then the tenant
POST/tenants/{tenant}/archivetenants.archiveSets archived_at; independent of soft delete
POST/tenants/{tenant}/unarchivetenants.archiveClears archived_at
GET/tenants/{tenant}/entitlementstenants.read{ core, modules } — licensing only
POST/tenants/{tenant}/modulesmodule-subscriptions.createInstall module — body: module_id, optional billing_cycle
GET/tenants/{tenant}/invoicesinvoices.listPaginated workspace invoices
GET/tenants/{tenant}/paymentspayments.listPaginated workspace payments
POST/tenants/{tenant}/impersonateimpersonation.startBody: reason (required, 5–1000 chars)

Tenant create/update body: company_name, workspace_name?, slug?, email, phone?, logo? (image upload), notes?, status?, timezone?, currency?, country?, locale?. Multipart form-data is supported for logo uploads. Response includes logo_path and logo_url. Platform domain is auto-generated from the slug + PLATFORM_DOMAIN_SUFFIXES (client domain values are ignored). Custom domains are tenant self-service via the Branded module.

Users

MethodPathPermissionNotes
POST/users/inviteusers.inviteCreates the user, assigns roles, emails an invite, sets invite_token/invite_sent_at
GET/usersusers.list
POST/usersusers.create
GET/users/{user}users.read (or self)
PUT/PATCH/users/{user}users.update
DELETE/users/{user}users.delete
POST/users/{user}/restoreusers.restore
DELETE/users/{user}/forceusers.force.delete
POST/users/{user}/suspendusers.suspend
POST/users/{user}/unsuspendusers.unsuspend
POST/users/{user}/change-passwordusers.reset-password
GET/users/{user}/activityusers.read (or self)Up to 50 recent spatie/laravel-activitylog entries for the user

Invite body: name, email, phone?, role[] (role names, at least one).

Create/update body adds: phone?, avatar_path?, password (create), role[].

Roles

MethodPathPermissionNotes
GET/roles/permissions-matrixroles.listAll permissions grouped by prefix, with the list of role names holding each one
GET/rolesroles.list
POST/rolesroles.create
GET/roles/{role}roles.readReturns the role plus every permission with an is_assigned flag
PUT/PATCH/roles/{role}roles.updateBody: name, permissions[] (permission IDs)
DELETE/roles/{role}roles.deleteBlocked for protected roles (config('central-protected-roles'))
POST/roles/{role}/cloneroles.cloneCopies all permissions to a new role; optional name in body, otherwise auto-generated

permissions-matrix response shape: [{ "id", "name", "group", "roles": ["admin", "manager", ...] }], where group is the permission name's prefix before the first . (e.g. tenants for tenants.archive).

Catalog (admin)

ResourcePathsNotes
ModulesCRUD + restore/forceFull catalog admin. Fields include uuid, pricing (monthly_price, yearly_price, currency), status, is_default_included, is_billableno payment-provider IDs

Default-included modules (Leads, Tasks, Communication Templates) cannot be deleted while marked is_default_included. Modules with workspace subscriptions cannot be deleted until those subscriptions are removed.

Provider price mappings are managed under Payment Gateways (GET/PUT /payment-gateways/{id}/module-prices), not on Modules. Features catalog has been removed — modules are licensing products; Spatie permissions handle authorization.

Marketplace

Published modules only. Permission: modules.list / modules.read.

MethodPathNotes
GET/marketplace/modulesPaginated; filters: search, category_id
GET/marketplace/modules/{module}Detail + already_installed, required_modules, optional_modules, missing_required_modules; optional ?tenant_id=

Install for a workspace: POST /tenants/{tenant}/modules (not a separate marketplace purchase endpoint).

Module subscriptions

MethodPathPermissionNotes
GET/module-subscriptionsmodule-subscriptions.listFilters: tenant_id, status, source
GET/module-subscriptions/{module_subscription}module-subscriptions.readIncludes module, tenant, history
POST/module-subscriptions/{module_subscription}/cancelmodule-subscriptions.updatePurchased modules only; included modules rejected
POST/module-subscriptions/{module_subscription}/deactivatemodule-subscriptions.deactivatePlatform-admin suspend (works on included modules)

Financial ledger (read-only)

MethodPathPermissionNotes
GET/invoicesinvoices.listPlatform-wide paginated list
GET/invoices/{invoice}invoices.readIncludes tenant, items, payments
GET/paymentspayments.listPlatform-wide paginated list
GET/payments/{payment}payments.readIncludes tenant, invoice, transactions

Tenant-scoped lists: GET /tenants/{tenant}/invoices, GET /tenants/{tenant}/payments.

Invoices are created by the Billing Engine (consolidated run or purchase settlement) — no public write endpoints.

Payment gateways

MethodPathPermissionNotes
GET/payment-gatewayspayment-gateways.listList providers
GET/payment-gateways/{id}payment-gateways.readRedacted config (secrets never returned)
POST/payment-gateways/{id}/enablepayment-gateways.update
POST/payment-gateways/{id}/disablepayment-gateways.updateRejects if default
POST/payment-gateways/{id}/defaultpayment-gateways.updateSyncs default_payment_gateway setting
PUT/payment-gateways/{id}/configpayment-gateways.updateMerge encrypted credentials
PUT/payment-gateways/{id}/modepayment-gateways.updatesandbox | live
POST/payment-gateways/{id}/test-connectionpayment-gateways.updateDriver probe
GET/payment-gateways/{id}/webhook-statuspayment-gateways.read
GET/payment-gateways/{id}/logspayment-gateways.readOperational logs
GET/payment-gateways/{id}/webhook-logspayment-gateways.read
GET/payment-gateways/{id}/capabilitiespayment-gateways.readCapabilities + currencies + requires_product_mapping
GET/payment-gateways/{id}/module-pricespayment-gateways.readGateway ↔ module product/price mappings
PUT/payment-gateways/{id}/module-pricespayment-gateways.updateReplace mappings ({ mappings: [...] }); 422 if gateway does not require mapping

Also accepts billing.manage as an alternate permission.

Impersonation

MethodPathPermissionNotes
POST/tenants/{tenant}/impersonateimpersonation.startCreates session; audits reason, IP, user-agent
POST/impersonation/{impersonation}/endimpersonation.end (or session owner)Sets ended_at, duration_seconds

Returns session metadata only — tenant-app login token exchange is out of scope for Central v1.

Stripe / gateway / email webhooks

MethodPathNotes
POST/stripe/webhookCashier-compatible path (config('cashier.path') + /webhook)
POST/webhooks/gateways/{code}Gateway-agnostic ingress for all drivers
POST/webhooks/email/{provider}Email delivery webhooks (Central)
POST/webhooks/email/{provider}/{tenant}Email delivery webhooks (Tenant custom mail)

Not under /api/central/v1. Payment paths normalize via PaymentGatewayInterface::parseWebhook() into BillingEngine. Email paths use SupportsWebhooks drivers. CSRF-exempt; rate-limited. Stripe Cashier route additionally syncs Cashier mirror tables.

System

MethodPathNotes
GET/public/settingsUnauthenticated bootstrap (branding, formats, registration/maintenance flags). No secrets.
POST/public/register-workspaceSelf-service workspace create when registration_enabled; otherwise 403 with dedicated message. Body: company_name, owner_name, email, password (+ confirmation); platform domain auto-generated from slug (client domain ignored).
GET/system-settingsAll admin settings (secrets masked). Response meta.mail_webhook includes webhook URL + event catalog when the active provider supports webhooks.
PUT/system-settings{ "settings": { "key": value } } — per-key validation; may include mail_webhook_events / mail_webhook_secret
POST/system-settings/test-mail{ "email": "…" } — sends test mail using runtime SMTP config
POST/system-settings/branding/{logo|favicon}Multipart file upload → stores via FileUploadService on the configured uploads disk
GET/email-logsPaginated delivery logs (has_body; bodies omitted)
GET/email-logs/{uuid}Log detail including body_html / body_text
POST/email-logs/{uuid}/resendResend from stored body (email-logs.resend, throttle 6/min)

Settings groups: general, localization, mail, branding, security, maintenance, billing.

Consumed keys

GroupKeysRuntime use
generalapp_name, company_name, timezone, locale, currency, registration_enabledApp title/config, tenant defaults, self-service registration
localizationdate_format, time_formatCentral SPA formatters
mailmail_provider, SMTP / Postmark / Mailgun credentials, mail_webhook_secret, mail_webhook_events, From identityLaravel mail + delivery webhooks
brandingbutton_color, support_email, logo_path, favicon_pathSPA CSS/document.title/sidebar; support footer on tenant-facing emails
securitysession_lifetime_minutes, password_min_length, password_require_specialSession lifetime; centralized PasswordRule / Password::defaults()
maintenancemaintenance_mode, maintenance_message, maintenance_etaTenant Application only (tenant.available middleware). Central stays up.
billinginvoice_prefix, proration_mode, default_payment_gateway, trial_enabled, stripe_enabled, stripe_webhook_configuredBilling engine / invoices

Removed: primary_color, feature_registration, feature_invites, queue_connection_display, filesystem_disk.

Dashboard payload

GET /dashboard returns workspace stats, module subscription status counts, revenue (MRR from billable active subscriptions), growth series, recent tenants, recent module subscriptions, recent activities.

Series shapes (frontend contract):

  • growth[]: { month: "YYYY-MM", count: number }
  • revenue_series[]: { month: "YYYY-MM", amount: number } (zeros until paid modules exist)

Permissions

Seeded by Database\Seeders\Central\PermissionsSeeder, guard central-api.

GroupPermissions
userslist, create, read, update, delete, restore, force.delete, suspend, unsuspend, invite, reset-password
tenantslist, create, read, update, delete, restore, force.delete, archive
roleslist, create, read, update, delete, clone
dashboardview
billingmanage
moduleslist, create, read, update, delete, restore, force.delete
module-subscriptionslist, create, read, update, delete, deactivate
invoiceslist, read, update
paymentslist, read, update
impersonationstart, end, list
system-settingslist, update

Removed

  • /features (+ restore/force)
  • /plans, /plans/{plan}/modules|features|limits
  • /limit-definitions
  • /tenant-subscriptions (+ cancel/resume/suspend)
  • /subscriptions, /setting-definitions

Artisan

CommandNotes
billing:run-consolidatedDaily scheduled; invoices all due workspaces

Postman: SaaS-Backend/.docs/postman/Central.postman_collection.json (refresh after API changes).

Official documentation for the SaleOS SaaS Platform.