php-tenant/docs/openapi.yaml
Claude 84cabeffd4
docs: add OpenAPI 3.0 specification for all API endpoints
Comprehensive machine-readable API documentation covering all REST
endpoints exposed by the php-tenant package:

- Workspace API (CRUD, switching, session and API key auth)
- Entitlement Provisioning API (Blesta: create, suspend, unsuspend, cancel, renew)
- Cross-App Entitlement API (check, usage recording, summary)
- Entitlement Webhooks API (CRUD, test, secret rotation, circuit breaker, deliveries)

Includes full request/response schemas, authentication details, error
responses, and pagination structures.

Fixes #33

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:49:58 +00:00

1830 lines
52 KiB
YAML

openapi: 3.0.3
info:
title: php-tenant API
description: |
REST API for the Core PHP Tenant module. Provides workspace management,
entitlement provisioning, cross-app entitlement checks, and webhook management.
## Authentication
Endpoints use one of two authentication methods:
- **Session auth** (`auth` middleware) — cookie-based session authentication
for browser clients.
- **API key auth** (`api.auth` middleware) — pass an API key via
`Authorization: Bearer hk_xxx` header. Used for server-to-server
communication and external integrations.
Provisioning endpoints (Blesta) require API key auth with
`entitlements.write` scope.
## Rate Limits
Provisioning endpoints are rate-limited to 60 requests per minute per API key.
version: 1.0.0
contact:
name: Core Team
license:
name: Proprietary
servers:
- url: /api
description: API base path (session-authenticated workspace routes)
- url: /api/v1
description: API v1 base path (cross-app entitlement routes)
- url: /api/provisioning
description: Provisioning base path (Blesta entitlement routes)
tags:
- name: Workspaces
description: Workspace CRUD and switching (session auth)
- name: Workspaces (API Key)
description: Read-only workspace access via API key
- name: Entitlement Provisioning
description: >-
Blesta provisioning API for creating, suspending, cancelling, and
renewing entitlements. Requires API key with entitlements.write scope.
- name: Cross-App Entitlements
description: >-
Cross-application entitlement checks and usage recording.
Used by external services (e.g. BioHost) to verify feature access.
- name: Entitlement Webhooks
description: >-
Webhook management for entitlement events. Supports CRUD, testing,
secret rotation, circuit-breaker reset, and delivery history.
paths:
# ==========================================================================
# Workspaces API (Session Auth)
# ==========================================================================
/workspaces:
get:
operationId: listWorkspaces
summary: List workspaces
description: List all workspaces the authenticated user has access to.
tags: [Workspaces]
security:
- sessionAuth: []
parameters:
- name: type
in: query
description: Filter by workspace type.
schema:
type: string
enum: [personal, team, agency, custom]
- name: is_active
in: query
description: Filter by active status.
schema:
type: boolean
- name: search
in: query
description: Search workspaces by name.
schema:
type: string
- name: per_page
in: query
description: Results per page (max 100).
schema:
type: integer
default: 25
maximum: 100
responses:
'200':
description: Paginated list of workspaces.
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedWorkspaces'
'401':
$ref: '#/components/responses/Unauthenticated'
post:
operationId: createWorkspace
summary: Create a workspace
description: Create a new workspace. The authenticated user becomes the owner.
tags: [Workspaces]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateWorkspaceRequest'
responses:
'200':
description: Workspace created.
content:
application/json:
schema:
$ref: '#/components/schemas/WorkspaceResource'
'401':
$ref: '#/components/responses/Unauthenticated'
'422':
$ref: '#/components/responses/ValidationError'
/workspaces/current:
get:
operationId: getCurrentWorkspace
summary: Get current workspace
description: Get the user's currently active (default) workspace.
tags: [Workspaces]
security:
- sessionAuth: []
responses:
'200':
description: Current workspace.
content:
application/json:
schema:
$ref: '#/components/schemas/WorkspaceResource'
'401':
$ref: '#/components/responses/Unauthenticated'
'404':
description: No workspace found.
/workspaces/{workspace}:
get:
operationId: getWorkspace
summary: Get a workspace
description: Get a single workspace by ID. User must have access.
tags: [Workspaces]
security:
- sessionAuth: []
parameters:
- $ref: '#/components/parameters/WorkspaceId'
responses:
'200':
description: Workspace details.
content:
application/json:
schema:
$ref: '#/components/schemas/WorkspaceResource'
'401':
$ref: '#/components/responses/Unauthenticated'
'404':
$ref: '#/components/responses/NotFound'
put:
operationId: updateWorkspace
summary: Update a workspace
description: Update workspace details. Requires owner or admin role.
tags: [Workspaces]
security:
- sessionAuth: []
parameters:
- $ref: '#/components/parameters/WorkspaceId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateWorkspaceRequest'
responses:
'200':
description: Workspace updated.
content:
application/json:
schema:
$ref: '#/components/schemas/WorkspaceResource'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
delete:
operationId: deleteWorkspace
summary: Delete a workspace
description: >-
Delete a workspace. Only the owner can delete. Cannot delete the
user's only workspace.
tags: [Workspaces]
security:
- sessionAuth: []
parameters:
- $ref: '#/components/parameters/WorkspaceId'
responses:
'204':
description: Workspace deleted.
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'422':
description: Cannot delete the user's only workspace.
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: cannot_delete
message:
type: string
example: You cannot delete your only workspace.
/workspaces/{workspace}/switch:
post:
operationId: switchWorkspace
summary: Switch to a workspace
description: Set a workspace as the user's default (active) workspace.
tags: [Workspaces]
security:
- sessionAuth: []
parameters:
- $ref: '#/components/parameters/WorkspaceId'
responses:
'200':
description: Switched to workspace.
content:
application/json:
schema:
$ref: '#/components/schemas/WorkspaceResource'
'401':
$ref: '#/components/responses/Unauthenticated'
'404':
$ref: '#/components/responses/NotFound'
# ==========================================================================
# Workspaces API (API Key Auth — read-only)
# ==========================================================================
/workspaces#apikey:
get:
operationId: listWorkspacesApiKey
summary: List workspaces (API key)
description: List workspaces accessible to the API key holder.
tags: [Workspaces (API Key)]
security:
- apiKeyAuth: []
parameters:
- name: type
in: query
schema:
type: string
enum: [personal, team, agency, custom]
- name: is_active
in: query
schema:
type: boolean
- name: search
in: query
schema:
type: string
- name: per_page
in: query
schema:
type: integer
default: 25
maximum: 100
responses:
'200':
description: Paginated list of workspaces.
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedWorkspaces'
'401':
$ref: '#/components/responses/Unauthenticated'
/workspaces/current#apikey:
get:
operationId: getCurrentWorkspaceApiKey
summary: Get current workspace (API key)
description: Get the current workspace for the API key holder.
tags: [Workspaces (API Key)]
security:
- apiKeyAuth: []
responses:
'200':
description: Current workspace.
content:
application/json:
schema:
$ref: '#/components/schemas/WorkspaceResource'
'401':
$ref: '#/components/responses/Unauthenticated'
/workspaces/{workspace}#apikey:
get:
operationId: getWorkspaceApiKey
summary: Get a workspace (API key)
description: Get a single workspace by ID via API key.
tags: [Workspaces (API Key)]
security:
- apiKeyAuth: []
parameters:
- $ref: '#/components/parameters/WorkspaceId'
responses:
'200':
description: Workspace details.
content:
application/json:
schema:
$ref: '#/components/schemas/WorkspaceResource'
'401':
$ref: '#/components/responses/Unauthenticated'
'404':
$ref: '#/components/responses/NotFound'
# ==========================================================================
# Entitlement Provisioning API (Blesta)
# ==========================================================================
/entitlements:
post:
operationId: createEntitlement
summary: Create an entitlement
description: >-
Provision a new entitlement for a workspace. Finds or creates the user
by email, creates a workspace if needed, and provisions the package.
New users receive an email verification notification.
tags: [Entitlement Provisioning]
security:
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateEntitlementRequest'
responses:
'201':
description: Entitlement created.
content:
application/json:
schema:
$ref: '#/components/schemas/CreateEntitlementResponse'
'404':
description: Package not found.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
$ref: '#/components/responses/ValidationError'
/entitlements/{id}:
get:
operationId: getEntitlement
summary: Get entitlement details
description: Retrieve full details for an entitlement by ID.
tags: [Entitlement Provisioning]
security:
- apiKeyAuth: []
parameters:
- $ref: '#/components/parameters/EntitlementId'
responses:
'200':
description: Entitlement details.
content:
application/json:
schema:
$ref: '#/components/schemas/EntitlementDetailResponse'
'404':
description: Entitlement not found.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/entitlements/{id}/suspend:
post:
operationId: suspendEntitlement
summary: Suspend an entitlement
description: Suspend an active entitlement. Optionally provide a reason.
tags: [Entitlement Provisioning]
security:
- apiKeyAuth: []
parameters:
- $ref: '#/components/parameters/EntitlementId'
requestBody:
content:
application/json:
schema:
type: object
properties:
reason:
type: string
description: Reason for suspension.
example: Non-payment
responses:
'200':
description: Entitlement suspended.
content:
application/json:
schema:
$ref: '#/components/schemas/EntitlementActionResponse'
'404':
description: Entitlement not found.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/entitlements/{id}/unsuspend:
post:
operationId: unsuspendEntitlement
summary: Unsuspend an entitlement
description: Reactivate a suspended entitlement.
tags: [Entitlement Provisioning]
security:
- apiKeyAuth: []
parameters:
- $ref: '#/components/parameters/EntitlementId'
responses:
'200':
description: Entitlement reactivated.
content:
application/json:
schema:
$ref: '#/components/schemas/EntitlementActionResponse'
'404':
description: Entitlement not found.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/entitlements/{id}/cancel:
post:
operationId: cancelEntitlement
summary: Cancel an entitlement
description: Cancel an entitlement. Optionally provide a reason.
tags: [Entitlement Provisioning]
security:
- apiKeyAuth: []
parameters:
- $ref: '#/components/parameters/EntitlementId'
requestBody:
content:
application/json:
schema:
type: object
properties:
reason:
type: string
description: Reason for cancellation.
example: Customer request
responses:
'200':
description: Entitlement cancelled.
content:
application/json:
schema:
$ref: '#/components/schemas/EntitlementActionResponse'
'404':
description: Entitlement not found.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/entitlements/{id}/renew:
post:
operationId: renewEntitlement
summary: Renew an entitlement
description: >-
Renew an entitlement by extending the expiry date and/or updating the
billing cycle anchor. Cycle-bound boosts from the previous cycle are
expired.
tags: [Entitlement Provisioning]
security:
- apiKeyAuth: []
parameters:
- $ref: '#/components/parameters/EntitlementId'
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/RenewEntitlementRequest'
responses:
'200':
description: Entitlement renewed.
content:
application/json:
schema:
$ref: '#/components/schemas/RenewEntitlementResponse'
'404':
description: Entitlement not found.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
# ==========================================================================
# Cross-App Entitlement API
# ==========================================================================
/entitlements/check:
get:
operationId: checkEntitlement
summary: Check feature access
description: >-
Check whether a user/workspace is entitled to use a specific feature.
Used by external applications (e.g. BioHost) to verify access before
performing an action.
tags: [Cross-App Entitlements]
security:
- apiKeyAuth: []
parameters:
- name: email
in: query
required: true
description: User email to look up the workspace.
schema:
type: string
format: email
- name: feature
in: query
required: true
description: Feature code to check (e.g. `social.accounts`).
schema:
type: string
- name: quantity
in: query
description: Quantity to check against the limit (default 1).
schema:
type: integer
minimum: 1
default: 1
responses:
'200':
description: Entitlement check result.
content:
application/json:
schema:
$ref: '#/components/schemas/EntitlementCheckResponse'
'404':
description: User or workspace not found.
content:
application/json:
schema:
type: object
properties:
allowed:
type: boolean
example: false
reason:
type: string
example: User not found
feature_code:
type: string
example: social.accounts
'422':
$ref: '#/components/responses/ValidationError'
/entitlements/usage:
post:
operationId: recordUsage
summary: Record feature usage
description: >-
Record usage for a feature after an action is performed. Used by
external applications to track consumption against entitlement limits.
tags: [Cross-App Entitlements]
security:
- apiKeyAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RecordUsageRequest'
responses:
'201':
description: Usage recorded.
content:
application/json:
schema:
$ref: '#/components/schemas/RecordUsageResponse'
'404':
description: User or workspace not found.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'422':
$ref: '#/components/responses/ValidationError'
/entitlements/summary:
get:
operationId: myEntitlementSummary
summary: Get my entitlement summary
description: >-
Get the usage summary for the authenticated user's default workspace.
Returns active packages, feature usage by category, and active boosts.
tags: [Cross-App Entitlements]
security:
- sessionAuth: []
responses:
'200':
description: Entitlement summary.
content:
application/json:
schema:
$ref: '#/components/schemas/EntitlementSummaryResponse'
'401':
$ref: '#/components/responses/Unauthenticated'
'404':
description: No workspace found for user.
/entitlements/summary/{workspace}:
get:
operationId: workspaceEntitlementSummary
summary: Get workspace entitlement summary
description: >-
Get the usage summary for a specific workspace. Returns active
packages, feature usage grouped by category, and active boosts.
tags: [Cross-App Entitlements]
security:
- sessionAuth: []
parameters:
- name: workspace
in: path
required: true
description: Workspace ID.
schema:
type: integer
responses:
'200':
description: Entitlement summary.
content:
application/json:
schema:
$ref: '#/components/schemas/EntitlementSummaryResponse'
'401':
$ref: '#/components/responses/Unauthenticated'
'404':
$ref: '#/components/responses/NotFound'
# ==========================================================================
# Entitlement Webhooks API
# ==========================================================================
/entitlement-webhooks:
get:
operationId: listWebhooks
summary: List webhooks
description: >-
List all entitlement webhooks for the current workspace. Supports
pagination.
tags: [Entitlement Webhooks]
security:
- sessionAuth: []
parameters:
- name: workspace_id
in: query
description: >-
Explicit workspace ID. Falls back to the user's default workspace
if omitted.
schema:
type: integer
- name: per_page
in: query
description: Results per page.
schema:
type: integer
default: 25
responses:
'200':
description: Paginated list of webhooks.
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedWebhooks'
'401':
$ref: '#/components/responses/Unauthenticated'
post:
operationId: createWebhook
summary: Create a webhook
description: >-
Register a new entitlement webhook. The webhook URL is validated
against SSRF attacks. Returns the webhook secret on creation (the
only time it is exposed).
tags: [Entitlement Webhooks]
security:
- sessionAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateWebhookRequest'
responses:
'201':
description: Webhook created.
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Webhook created successfully
webhook:
$ref: '#/components/schemas/Webhook'
secret:
type: string
description: >-
The webhook signing secret. Only returned on creation.
example: whsec_a1b2c3d4e5f6...
'401':
$ref: '#/components/responses/Unauthenticated'
'422':
description: Validation error or invalid webhook URL.
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/ValidationErrorBody'
- type: object
properties:
message:
type: string
error:
type: string
example: invalid_webhook_url
/entitlement-webhooks/events:
get:
operationId: listWebhookEvents
summary: List available event types
description: Get the list of event types that webhooks can subscribe to.
tags: [Entitlement Webhooks]
security:
- sessionAuth: []
responses:
'200':
description: Available event types.
content:
application/json:
schema:
type: object
properties:
events:
type: object
description: >-
Map of event codes to human-readable descriptions.
additionalProperties:
type: string
example:
limit_warning: Limit warning threshold reached
limit_reached: Feature limit reached
package_changed: Package added or changed
boost_activated: Boost activated
boost_expired: Boost expired
/entitlement-webhooks/{webhook}:
get:
operationId: getWebhook
summary: Get a webhook
description: >-
Get webhook details including delivery count and the 10 most recent
deliveries.
tags: [Entitlement Webhooks]
security:
- sessionAuth: []
parameters:
- $ref: '#/components/parameters/WebhookId'
responses:
'200':
description: Webhook details with recent deliveries.
content:
application/json:
schema:
type: object
properties:
webhook:
$ref: '#/components/schemas/Webhook'
available_events:
type: object
additionalProperties:
type: string
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
put:
operationId: updateWebhook
summary: Update a webhook
description: Update webhook settings. All fields are optional.
tags: [Entitlement Webhooks]
security:
- sessionAuth: []
parameters:
- $ref: '#/components/parameters/WebhookId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateWebhookRequest'
responses:
'200':
description: Webhook updated.
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Webhook updated successfully
webhook:
$ref: '#/components/schemas/Webhook'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'422':
description: Validation error or invalid webhook URL.
delete:
operationId: deleteWebhook
summary: Delete a webhook
description: Permanently delete a webhook and its delivery history.
tags: [Entitlement Webhooks]
security:
- sessionAuth: []
parameters:
- $ref: '#/components/parameters/WebhookId'
responses:
'200':
description: Webhook deleted.
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Webhook deleted successfully
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
/entitlement-webhooks/{webhook}/test:
post:
operationId: testWebhook
summary: Send a test webhook
description: >-
Send a test event to the webhook URL. The URL is re-validated against
SSRF before the request is made.
tags: [Entitlement Webhooks]
security:
- sessionAuth: []
parameters:
- $ref: '#/components/parameters/WebhookId'
responses:
'200':
description: Test delivery result.
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Test webhook sent successfully
delivery:
$ref: '#/components/schemas/WebhookDelivery'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'422':
description: Invalid webhook URL (SSRF blocked).
content:
application/json:
schema:
type: object
properties:
message:
type: string
error:
type: string
example: invalid_webhook_url
reason:
type: string
/entitlement-webhooks/{webhook}/regenerate-secret:
post:
operationId: regenerateWebhookSecret
summary: Regenerate webhook secret
description: Generate a new signing secret for the webhook.
tags: [Entitlement Webhooks]
security:
- sessionAuth: []
parameters:
- $ref: '#/components/parameters/WebhookId'
responses:
'200':
description: Secret regenerated.
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Secret regenerated successfully
secret:
type: string
description: The new webhook signing secret.
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
/entitlement-webhooks/{webhook}/reset-circuit-breaker:
post:
operationId: resetCircuitBreaker
summary: Reset circuit breaker
description: >-
Re-enable a webhook that was automatically disabled after consecutive
failures (circuit breaker tripped after 5 failures).
tags: [Entitlement Webhooks]
security:
- sessionAuth: []
parameters:
- $ref: '#/components/parameters/WebhookId'
responses:
'200':
description: Circuit breaker reset, webhook re-enabled.
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Webhook re-enabled successfully
webhook:
$ref: '#/components/schemas/Webhook'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
/entitlement-webhooks/{webhook}/deliveries:
get:
operationId: listWebhookDeliveries
summary: List webhook deliveries
description: Get the delivery history for a webhook, newest first.
tags: [Entitlement Webhooks]
security:
- sessionAuth: []
parameters:
- $ref: '#/components/parameters/WebhookId'
- name: per_page
in: query
description: Results per page.
schema:
type: integer
default: 50
responses:
'200':
description: Paginated delivery history.
content:
application/json:
schema:
$ref: '#/components/schemas/PaginatedDeliveries'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
/entitlement-webhooks/deliveries/{delivery}/retry:
post:
operationId: retryDelivery
summary: Retry a failed delivery
description: >-
Retry a previously failed webhook delivery. Cannot retry a delivery
that already succeeded.
tags: [Entitlement Webhooks]
security:
- sessionAuth: []
parameters:
- name: delivery
in: path
required: true
description: Delivery ID.
schema:
type: integer
responses:
'200':
description: Retry result.
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Delivery retried successfully
delivery:
$ref: '#/components/schemas/WebhookDelivery'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'422':
description: Cannot retry a successful delivery.
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Cannot retry a successful delivery
# ==============================================================================
# Components
# ==============================================================================
components:
securitySchemes:
sessionAuth:
type: apiKey
in: cookie
name: session
description: Cookie-based session authentication.
apiKeyAuth:
type: http
scheme: bearer
description: >-
API key authentication. Pass your key as a Bearer token
(`Authorization: Bearer hk_xxx`).
parameters:
WorkspaceId:
name: workspace
in: path
required: true
description: Workspace ID.
schema:
type: integer
EntitlementId:
name: id
in: path
required: true
description: Entitlement (workspace package) ID.
schema:
type: integer
WebhookId:
name: webhook
in: path
required: true
description: Webhook ID.
schema:
type: integer
responses:
Unauthenticated:
description: Authentication required.
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: Unauthenticated
Forbidden:
description: Insufficient permissions.
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: You do not have permission to perform this action.
NotFound:
description: Resource not found.
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Not found
ValidationError:
description: Validation failed.
content:
application/json:
schema:
$ref: '#/components/schemas/ValidationErrorBody'
schemas:
# ------------------------------------------------------------------
# Workspace schemas
# ------------------------------------------------------------------
WorkspaceResource:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: "Acme Corp"
slug:
type: string
example: acme-corp-a1b2c3
icon:
type: string
nullable: true
example: fa-building
color:
type: string
nullable: true
example: "#3b82f6"
description:
type: string
nullable: true
example: Main workspace for Acme Corp
type:
type: string
enum: [personal, team, agency, custom]
example: custom
is_active:
type: boolean
example: true
users_count:
type: integer
example: 5
bio_pages_count:
type: integer
example: 12
CreateWorkspaceRequest:
type: object
required: [name]
properties:
name:
type: string
maxLength: 255
example: My Workspace
slug:
type: string
maxLength: 100
description: >-
URL-friendly identifier. Auto-generated from name if omitted.
example: my-workspace
icon:
type: string
maxLength: 50
nullable: true
color:
type: string
maxLength: 20
nullable: true
description:
type: string
maxLength: 500
nullable: true
type:
type: string
enum: [personal, team, agency, custom]
default: custom
UpdateWorkspaceRequest:
type: object
properties:
name:
type: string
maxLength: 255
slug:
type: string
maxLength: 100
icon:
type: string
maxLength: 50
nullable: true
color:
type: string
maxLength: 20
nullable: true
description:
type: string
maxLength: 500
nullable: true
is_active:
type: boolean
PaginatedWorkspaces:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/WorkspaceResource'
links:
$ref: '#/components/schemas/PaginationLinks'
meta:
$ref: '#/components/schemas/PaginationMeta'
# ------------------------------------------------------------------
# Entitlement Provisioning schemas
# ------------------------------------------------------------------
CreateEntitlementRequest:
type: object
required: [email, name, product_code]
properties:
email:
type: string
format: email
description: Client email. User is found or created by this address.
example: client@example.com
name:
type: string
maxLength: 255
description: Client name. Used when creating a new user.
example: Jane Doe
product_code:
type: string
description: Package code to provision.
example: starter-plan
billing_cycle_anchor:
type: string
format: date-time
nullable: true
description: Billing cycle anchor date (ISO 8601).
expires_at:
type: string
format: date-time
nullable: true
description: Entitlement expiry date (ISO 8601).
blesta_service_id:
type: string
nullable: true
description: External Blesta service ID for cross-referencing.
CreateEntitlementResponse:
type: object
properties:
success:
type: boolean
example: true
entitlement_id:
type: integer
example: 42
workspace_id:
type: integer
example: 7
workspace_slug:
type: string
example: jane-doe-a1b2c3
package:
type: string
example: starter-plan
status:
type: string
enum: [active, suspended, cancelled, expired]
example: active
EntitlementDetailResponse:
type: object
properties:
success:
type: boolean
example: true
entitlement:
type: object
properties:
id:
type: integer
example: 42
workspace_id:
type: integer
example: 7
workspace_slug:
type: string
example: jane-doe-a1b2c3
package_code:
type: string
example: starter-plan
package_name:
type: string
example: Starter Plan
status:
type: string
enum: [active, suspended, cancelled, expired]
example: active
starts_at:
type: string
format: date-time
nullable: true
expires_at:
type: string
format: date-time
nullable: true
billing_cycle_anchor:
type: string
format: date-time
nullable: true
blesta_service_id:
type: string
nullable: true
EntitlementActionResponse:
type: object
properties:
success:
type: boolean
example: true
entitlement_id:
type: integer
example: 42
status:
type: string
enum: [active, suspended, cancelled, expired]
RenewEntitlementRequest:
type: object
properties:
expires_at:
type: string
format: date-time
nullable: true
description: New expiry date (ISO 8601).
billing_cycle_anchor:
type: string
format: date-time
nullable: true
description: New billing cycle anchor (ISO 8601).
RenewEntitlementResponse:
type: object
properties:
success:
type: boolean
example: true
entitlement_id:
type: integer
example: 42
status:
type: string
enum: [active, suspended, cancelled, expired]
expires_at:
type: string
format: date-time
nullable: true
# ------------------------------------------------------------------
# Cross-App Entitlement schemas
# ------------------------------------------------------------------
EntitlementCheckResponse:
type: object
properties:
allowed:
type: boolean
description: Whether the requested action is permitted.
example: true
limit:
type: integer
nullable: true
description: Total limit for the feature (null if unlimited).
example: 10
used:
type: integer
description: Current usage count.
example: 3
remaining:
type: integer
nullable: true
description: Remaining quota (null if unlimited).
example: 7
unlimited:
type: boolean
description: Whether the feature has no limit.
example: false
usage_percentage:
type: number
format: float
description: Usage as a percentage of the limit (0-100).
example: 30.0
feature_code:
type: string
example: social.accounts
workspace_id:
type: integer
example: 7
RecordUsageRequest:
type: object
required: [email, feature]
properties:
email:
type: string
format: email
description: User email to look up the workspace.
example: client@example.com
feature:
type: string
description: Feature code to record usage for.
example: social.posts.scheduled
quantity:
type: integer
minimum: 1
default: 1
description: Number of units consumed.
metadata:
type: object
nullable: true
description: Arbitrary metadata to attach to the usage record.
additionalProperties: true
example:
post_id: "abc123"
platform: twitter
RecordUsageResponse:
type: object
properties:
success:
type: boolean
example: true
usage_record_id:
type: integer
example: 891
feature_code:
type: string
example: social.posts.scheduled
quantity:
type: integer
example: 1
EntitlementSummaryResponse:
type: object
properties:
workspace_id:
type: integer
example: 7
packages:
type: array
items:
type: object
properties:
code:
type: string
example: starter-plan
name:
type: string
example: Starter Plan
status:
type: string
enum: [active, suspended, cancelled, expired]
expires_at:
type: string
format: date-time
nullable: true
features:
type: object
description: >-
Feature usage grouped by category. Each category key maps to an
array of feature objects.
additionalProperties:
type: array
items:
type: object
properties:
code:
type: string
example: social.accounts
name:
type: string
example: Social Accounts
limit:
type: integer
nullable: true
used:
type: integer
remaining:
type: integer
nullable: true
unlimited:
type: boolean
percentage:
type: number
format: float
example:
social:
- code: social.accounts
name: Social Accounts
limit: 10
used: 3
remaining: 7
unlimited: false
percentage: 30.0
boosts:
type: array
items:
type: object
properties:
feature:
type: string
example: social.accounts
value:
type: integer
example: 5
type:
type: string
example: add
expires_at:
type: string
format: date-time
nullable: true
# ------------------------------------------------------------------
# Webhook schemas
# ------------------------------------------------------------------
Webhook:
type: object
properties:
id:
type: integer
example: 1
uuid:
type: string
format: uuid
workspace_id:
type: integer
name:
type: string
example: My Webhook
url:
type: string
format: uri
example: https://example.com/webhooks/entitlements
events:
type: array
items:
type: string
enum:
- limit_warning
- limit_reached
- package_changed
- boost_activated
- boost_expired
is_active:
type: boolean
example: true
max_attempts:
type: integer
example: 3
last_delivery_status:
type: string
enum: [pending, success, failed]
nullable: true
last_triggered_at:
type: string
format: date-time
nullable: true
failure_count:
type: integer
example: 0
metadata:
type: object
nullable: true
additionalProperties: true
deliveries_count:
type: integer
description: Total number of deliveries (when loaded).
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
CreateWebhookRequest:
type: object
required: [name, url, events]
properties:
name:
type: string
maxLength: 255
example: Production Webhook
url:
type: string
format: uri
maxLength: 2048
description: >-
HTTPS endpoint URL. Validated against SSRF (internal network
addresses are rejected).
example: https://example.com/webhooks/entitlements
events:
type: array
minItems: 1
items:
type: string
enum:
- limit_warning
- limit_reached
- package_changed
- boost_activated
- boost_expired
example: [limit_warning, limit_reached]
secret:
type: string
minLength: 32
nullable: true
description: >-
Custom signing secret. If omitted, one is auto-generated.
metadata:
type: object
nullable: true
additionalProperties: true
UpdateWebhookRequest:
type: object
properties:
name:
type: string
maxLength: 255
url:
type: string
format: uri
maxLength: 2048
events:
type: array
minItems: 1
items:
type: string
enum:
- limit_warning
- limit_reached
- package_changed
- boost_activated
- boost_expired
is_active:
type: boolean
max_attempts:
type: integer
minimum: 1
maximum: 10
metadata:
type: object
nullable: true
additionalProperties: true
WebhookDelivery:
type: object
properties:
id:
type: integer
uuid:
type: string
format: uuid
webhook_id:
type: integer
event:
type: string
example: limit_warning
attempts:
type: integer
example: 1
status:
type: string
enum: [pending, success, failed]
http_status:
type: integer
nullable: true
example: 200
resend_at:
type: string
format: date-time
nullable: true
resent_manually:
type: boolean
example: false
payload:
type: object
description: The JSON payload that was sent.
response:
type: object
nullable: true
description: The response body received from the endpoint.
created_at:
type: string
format: date-time
PaginatedWebhooks:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Webhook'
links:
$ref: '#/components/schemas/PaginationLinks'
meta:
$ref: '#/components/schemas/PaginationMeta'
PaginatedDeliveries:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/WebhookDelivery'
links:
$ref: '#/components/schemas/PaginationLinks'
meta:
$ref: '#/components/schemas/PaginationMeta'
# ------------------------------------------------------------------
# Shared schemas
# ------------------------------------------------------------------
ErrorResponse:
type: object
properties:
success:
type: boolean
example: false
error:
type: string
example: Resource not found
ValidationErrorBody:
type: object
properties:
message:
type: string
example: The given data was invalid.
errors:
type: object
description: >-
Field-level validation errors. Keys are field names, values are
arrays of error messages.
additionalProperties:
type: array
items:
type: string
example:
email:
- The email field is required.
name:
- The name field is required.
PaginationLinks:
type: object
properties:
first:
type: string
format: uri
nullable: true
last:
type: string
format: uri
nullable: true
prev:
type: string
format: uri
nullable: true
next:
type: string
format: uri
nullable: true
PaginationMeta:
type: object
properties:
current_page:
type: integer
from:
type: integer
nullable: true
last_page:
type: integer
per_page:
type: integer
to:
type: integer
nullable: true
total:
type: integer