Files
breadcrumb-the-shire/docs/openapi.yaml
2026-03-04 15:56:58 +01:00

2203 lines
57 KiB
YAML

openapi: 3.0.3
info:
title: Malphite API
version: v1
description: |
OpenAPI specification for the current `/api/v1` endpoints.
Authentication uses Bearer tokens in the format `<selector>:<token>`.
servers:
- url: /api/v1
security:
- BearerAuth: []
tags:
- name: auth
description: Public authentication endpoints (no Bearer token required)
- name: me
- name: tokens
- name: users
- name: tenants
- name: departments
- name: roles
- name: permissions
- name: settings
paths:
/auth/login:
post:
tags: [auth]
summary: Login with email and password
description: |
Authenticates a user with email and password and returns a Bearer token.
This endpoint is **public** — no Authorization header required.
The returned token can be used immediately as `Authorization: Bearer <token>`
for all subsequent API requests.
Rate limits (stricter than general API limits):
- 10 failed attempts per IP per 10 minutes
- 5 failed attempts per email+IP per 15 minutes
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LoginRequest'
example:
email: user@example.com
password: "..."
token_name: "My App"
responses:
'201':
description: Login successful — token issued
content:
application/json:
schema:
$ref: '#/components/schemas/LoginResponse'
'401':
description: Authentication failed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
invalid_credentials:
value:
ok: false
request_id: 550e8400-e29b-41d4-a716-446655440000
error_code: invalid_credentials
details: {}
account_inactive:
value:
ok: false
request_id: 550e8400-e29b-41d4-a716-446655440000
error_code: account_inactive
details: {}
'403':
$ref: '#/components/responses/Forbidden'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
/me:
get:
tags: [me]
summary: Get authenticated user profile
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/MeResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/RateLimited'
put:
tags: [me]
summary: Update authenticated user profile
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MeUpdateRequest'
responses:
'200':
description: Updated profile
content:
application/json:
schema:
$ref: '#/components/schemas/MeResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
patch:
tags: [me]
summary: Patch authenticated user profile
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MeUpdateRequest'
responses:
'200':
description: Updated profile
content:
application/json:
schema:
$ref: '#/components/schemas/MeResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
/me/password:
post:
tags: [me]
summary: Change password for authenticated user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MePasswordRequest'
responses:
'204':
description: No Content
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
/me/avatar:
get:
tags: [me]
summary: Get avatar image for authenticated user
parameters:
- $ref: '#/components/parameters/AvatarSize'
responses:
'200':
description: Avatar binary
content:
image/jpeg: {}
image/png: {}
image/webp: {}
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
post:
tags: [me]
summary: Upload avatar for authenticated user
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [avatar]
properties:
avatar:
type: string
format: binary
responses:
'201':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
delete:
tags: [me]
summary: Delete avatar for authenticated user
responses:
'204':
description: No Content
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/RateLimited'
/me/tokens:
get:
tags: [tokens]
summary: List current user API tokens
parameters:
- $ref: '#/components/parameters/Limit50'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/TokenListResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/RateLimited'
post:
tags: [tokens]
summary: Create API token for current user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TokenCreateRequest'
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/TokenCreateResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
/me/tokens/show/{uuid}:
parameters:
- $ref: '#/components/parameters/UuidPath'
get:
tags: [tokens]
summary: Get current user token by UUID
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/TokenShowResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
delete:
tags: [tokens]
summary: Revoke current user token by UUID
responses:
'204':
description: No Content
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'500':
$ref: '#/components/responses/ServerError'
'429':
$ref: '#/components/responses/RateLimited'
/me/tokens/rotate:
post:
tags: [tokens]
summary: Rotate currently used API token
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
expires_at:
type: string
format: date-time
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/TokenCreateResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
/me/tokens/revoke-all:
post:
tags: [tokens]
summary: Revoke all current user API tokens
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/RevokeAllResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/RateLimited'
/users:
get:
tags: [users]
summary: List users
parameters:
- $ref: '#/components/parameters/Limit25'
- $ref: '#/components/parameters/Offset'
- $ref: '#/components/parameters/Search'
- $ref: '#/components/parameters/OrderUsers'
- $ref: '#/components/parameters/Dir'
- $ref: '#/components/parameters/ActiveFilter'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/UserListResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/RateLimited'
post:
tags: [users]
summary: Create user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserCreateRequest'
responses:
'201':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
/users/show/{uuid}:
parameters:
- $ref: '#/components/parameters/UuidPath'
get:
tags: [users]
summary: Get user by UUID
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/UserShowResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
put:
tags: [users]
summary: Update user by UUID
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserUpdateRequest'
responses:
'200':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
patch:
tags: [users]
summary: Patch user by UUID
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserUpdateRequest'
responses:
'200':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
delete:
tags: [users]
summary: Delete user by UUID
responses:
'200':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
/users/avatar/{uuid}:
parameters:
- $ref: '#/components/parameters/UuidPath'
- $ref: '#/components/parameters/AvatarSize'
get:
tags: [users]
summary: Get avatar image by user UUID
responses:
'200':
description: Avatar binary
content:
image/jpeg: {}
image/png: {}
image/webp: {}
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
/tenants:
get:
tags: [tenants]
summary: List tenants
parameters:
- $ref: '#/components/parameters/Limit25'
- $ref: '#/components/parameters/Offset'
- $ref: '#/components/parameters/Search'
- $ref: '#/components/parameters/OrderTenants'
- $ref: '#/components/parameters/Dir'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/TenantListResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/RateLimited'
post:
tags: [tenants]
summary: Create tenant
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TenantWriteRequest'
responses:
'201':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
/tenants/show/{uuid}:
parameters:
- $ref: '#/components/parameters/UuidPath'
get:
tags: [tenants]
summary: Get tenant by UUID
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/TenantShowResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
put:
tags: [tenants]
summary: Update tenant by UUID
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TenantWriteRequest'
responses:
'200':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
patch:
tags: [tenants]
summary: Patch tenant by UUID
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TenantWriteRequest'
responses:
'200':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
delete:
tags: [tenants]
summary: Delete tenant by UUID
responses:
'200':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
/tenants/avatar/{uuid}:
parameters:
- $ref: '#/components/parameters/UuidPath'
- $ref: '#/components/parameters/AvatarSize'
get:
tags: [tenants]
summary: Get avatar image by tenant UUID
responses:
'200':
description: Avatar binary
content:
image/jpeg: {}
image/png: {}
image/webp: {}
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
/departments:
get:
tags: [departments]
summary: List departments
parameters:
- $ref: '#/components/parameters/Limit25'
- $ref: '#/components/parameters/Offset'
- $ref: '#/components/parameters/Search'
- $ref: '#/components/parameters/OrderDepartments'
- $ref: '#/components/parameters/Dir'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/DepartmentListResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/RateLimited'
post:
tags: [departments]
summary: Create department
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DepartmentWriteRequest'
responses:
'201':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
/departments/show/{uuid}:
parameters:
- $ref: '#/components/parameters/UuidPath'
get:
tags: [departments]
summary: Get department by UUID
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/DepartmentShowResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
put:
tags: [departments]
summary: Update department by UUID
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DepartmentWriteRequest'
responses:
'200':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
patch:
tags: [departments]
summary: Patch department by UUID
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/DepartmentWriteRequest'
responses:
'200':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
delete:
tags: [departments]
summary: Delete department by UUID
responses:
'200':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
/roles:
get:
tags: [roles]
summary: List roles
parameters:
- $ref: '#/components/parameters/Limit25'
- $ref: '#/components/parameters/Offset'
- $ref: '#/components/parameters/Search'
- $ref: '#/components/parameters/OrderRoles'
- $ref: '#/components/parameters/Dir'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/RoleListResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/RateLimited'
post:
tags: [roles]
summary: Create role
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RoleWriteRequest'
responses:
'201':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
/roles/show/{uuid}:
parameters:
- $ref: '#/components/parameters/UuidPath'
get:
tags: [roles]
summary: Get role by UUID
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/RoleShowResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
put:
tags: [roles]
summary: Update role by UUID
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RoleWriteRequest'
responses:
'200':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
patch:
tags: [roles]
summary: Patch role by UUID
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/RoleWriteRequest'
responses:
'200':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
delete:
tags: [roles]
summary: Delete role by UUID
responses:
'200':
$ref: '#/components/responses/DataObject'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/RateLimited'
/permissions:
get:
tags: [permissions]
summary: List permissions
parameters:
- $ref: '#/components/parameters/Limit25'
- $ref: '#/components/parameters/Offset'
- $ref: '#/components/parameters/Search'
- $ref: '#/components/parameters/OrderPermissions'
- $ref: '#/components/parameters/Dir'
- $ref: '#/components/parameters/ActiveFilter'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/PermissionListResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/RateLimited'
/settings:
get:
tags: [settings]
summary: Get application settings snapshot
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/SettingsResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'429':
$ref: '#/components/responses/RateLimited'
put:
tags: [settings]
summary: Update application settings
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SettingsUpdateRequest'
responses:
'200':
description: Updated settings snapshot
content:
application/json:
schema:
$ref: '#/components/schemas/SettingsResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
patch:
tags: [settings]
summary: Patch application settings
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/SettingsUpdateRequest'
responses:
'200':
description: Updated settings snapshot
content:
application/json:
schema:
$ref: '#/components/schemas/SettingsResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
/settings/public:
get:
tags: [settings]
summary: Get public app settings for unauthenticated clients
security: []
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/GenericDataResponse'
'429':
$ref: '#/components/responses/RateLimited'
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: selector:token
parameters:
UuidPath:
name: uuid
in: path
required: true
schema:
type: string
format: uuid
Limit25:
name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 25
Limit50:
name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 50
Offset:
name: offset
in: query
schema:
type: integer
minimum: 0
default: 0
Search:
name: search
in: query
schema:
type: string
Dir:
name: dir
in: query
schema:
type: string
enum: [asc, desc]
default: asc
ActiveFilter:
name: active
in: query
schema:
type: string
description: "Accepted aliases: 1, 0, true, false, active, inactive"
AvatarSize:
name: size
in: query
schema:
type: integer
minimum: 16
maximum: 1024
OrderUsers:
name: order
in: query
schema:
type: string
enum: [id, uuid, first_name, last_name, display_name, email, created, modified, active, last_login_at]
default: last_name
OrderTenants:
name: order
in: query
schema:
type: string
enum: [id, uuid, description, created, modified]
default: description
OrderDepartments:
name: order
in: query
schema:
type: string
enum: [id, uuid, description, code, cost_center, active, created, modified]
default: description
OrderRoles:
name: order
in: query
schema:
type: string
enum: [id, uuid, description, code, active, created, modified]
default: description
OrderPermissions:
name: order
in: query
schema:
type: string
enum: [key, description, active, created]
default: key
responses:
Unauthorized:
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
ok: false
request_id: 550e8400-e29b-41d4-a716-446655440000
error_code: unauthorized
details: {}
Forbidden:
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
NotFound:
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
ok: false
request_id: 550e8400-e29b-41d4-a716-446655440000
error_code: not_found
details: {}
ValidationError:
description: Validation failed
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
ok: false
request_id: 550e8400-e29b-41d4-a716-446655440000
error_code: validation_error
details:
errors:
field:
- invalid_value
Conflict:
description: Conflict
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
RateLimited:
description: Rate limited
headers:
Retry-After:
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
ok: false
request_id: 550e8400-e29b-41d4-a716-446655440000
error_code: rate_limit_exceeded
details:
retry_after: 60
ServerError:
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
DataObject:
description: Generic service response
content:
application/json:
schema:
$ref: '#/components/schemas/GenericDataResponse'
schemas:
ErrorResponse:
type: object
required: [ok, request_id, error_code, details]
properties:
ok:
type: boolean
example: false
request_id:
type: string
format: uuid
error_code:
type: string
details:
type: object
additionalProperties: true
GenericDataResponse:
type: object
required: [data]
properties:
data:
type: object
additionalProperties: true
LoginRequest:
type: object
additionalProperties: false
required: [email, password]
properties:
email:
type: string
format: email
description: User email address (case-insensitive)
password:
type: string
description: User password
token_name:
type: string
description: Optional label for the issued token (e.g. "iPhone App"). Defaults to "Login".
default: Login
tenant_uuid:
type: string
format: uuid
description: Optional — scope the issued token to a specific tenant UUID. Must be assigned to the user.
LoginResponse:
type: object
required: [data]
properties:
data:
type: object
required: [token, token_uuid, expires_at]
properties:
token:
type: string
description: Full Bearer token in format `selector:plaintext`. Shown only once — store securely.
example: "abc123def:456789..."
token_uuid:
type: string
format: uuid
description: UUID of the created token record (use for revocation via /me/tokens)
expires_at:
type: string
format: date-time
nullable: true
description: UTC expiry timestamp of the token
MeUpdateRequest:
type: object
additionalProperties: false
properties:
first_name:
type: string
last_name:
type: string
profile_description:
type: string
job_title:
type: string
phone:
type: string
mobile:
type: string
short_dial:
type: string
address:
type: string
postal_code:
type: string
city:
type: string
country:
type: string
region:
type: string
locale:
type: string
theme:
type: string
current_tenant_uuid:
type: string
format: uuid
MePasswordRequest:
type: object
additionalProperties: false
required: [current_password, password, password2]
properties:
current_password:
type: string
password:
type: string
password2:
type: string
PermissionListItem:
type: object
properties:
key:
type: string
description:
type: string
active:
type: boolean
is_system:
type: boolean
PermissionListResponse:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/PermissionListItem'
total:
type: integer
limit:
type: integer
offset:
type: integer
SettingsObject:
type: object
properties:
app_title:
type: string
app_locale:
type: string
app_theme:
type: string
app_theme_user_allowed:
type: boolean
app_registration_enabled:
type: boolean
app_primary_color:
type: string
api_token_default_ttl_days:
type: integer
nullable: true
api_token_max_ttl_days:
type: integer
nullable: true
api_cors_allowed_origins:
type: string
default_tenant_id:
type: integer
nullable: true
default_role_id:
type: integer
nullable: true
default_department_id:
type: integer
nullable: true
user_inactivity_deactivate_days:
type: integer
nullable: true
user_inactivity_delete_days:
type: integer
nullable: true
system_audit_enabled:
type: boolean
system_audit_retention_days:
type: integer
nullable: true
microsoft_shared_client_id:
type: string
microsoft_authority:
type: string
smtp_host:
type: string
smtp_port:
type: integer
nullable: true
smtp_user:
type: string
smtp_secure:
type: string
smtp_from:
type: string
smtp_from_name:
type: string
SettingsResponse:
type: object
required: [data]
properties:
data:
$ref: '#/components/schemas/SettingsObject'
SettingsUpdateRequest:
type: object
additionalProperties: false
properties:
app_title:
type: string
app_locale:
type: string
app_theme:
type: string
app_theme_user_allowed:
type: boolean
app_registration_enabled:
type: boolean
app_primary_color:
type: string
api_token_default_ttl_days:
type: integer
nullable: true
api_token_max_ttl_days:
type: integer
nullable: true
api_cors_allowed_origins:
type: string
default_tenant_id:
type: integer
nullable: true
default_role_id:
type: integer
nullable: true
default_department_id:
type: integer
nullable: true
user_inactivity_deactivate_days:
type: integer
nullable: true
user_inactivity_delete_days:
type: integer
nullable: true
system_audit_enabled:
type: boolean
system_audit_retention_days:
type: integer
nullable: true
microsoft_shared_client_id:
type: string
microsoft_authority:
type: string
smtp_host:
type: string
smtp_port:
type: integer
nullable: true
smtp_user:
type: string
smtp_password:
type: string
smtp_secure:
type: string
smtp_from:
type: string
smtp_from_name:
type: string
RoleWriteRequest:
type: object
additionalProperties: false
required: [description]
properties:
description:
type: string
minLength: 1
code:
type: string
active:
type: string
description: "Accepted aliases: 1, 0, true, false, active, inactive"
DepartmentWriteRequest:
type: object
additionalProperties: false
required: [description, tenant_uuid]
properties:
description:
type: string
minLength: 1
tenant_uuid:
type: string
format: uuid
code:
type: string
cost_center:
type: string
active:
type: string
description: "Accepted aliases: 1, 0, true, false, active, inactive"
TenantWriteRequest:
type: object
additionalProperties: false
required: [description, status]
properties:
description:
type: string
minLength: 1
status:
type: string
enum: [active, inactive]
address:
type: string
postal_code:
type: string
city:
type: string
country:
type: string
region:
type: string
vat_id:
type: string
tax_number:
type: string
phone:
type: string
fax:
type: string
email:
type: string
format: email
support_email:
type: string
format: email
support_phone:
type: string
billing_email:
type: string
format: email
website:
type: string
privacy_url:
type: string
imprint_url:
type: string
primary_color:
type: string
description: "Hex color, e.g. #0f766e"
primary_color_use_default:
type: boolean
default_theme:
type: string
allow_user_theme_mode:
type: string
enum: ['', '0', '1']
UserCreateRequest:
type: object
additionalProperties: false
required: [first_name, last_name, email, password, password2]
properties:
first_name:
type: string
minLength: 1
last_name:
type: string
minLength: 1
email:
type: string
format: email
profile_description:
type: string
job_title:
type: string
phone:
type: string
mobile:
type: string
short_dial:
type: string
address:
type: string
postal_code:
type: string
city:
type: string
country:
type: string
region:
type: string
hire_date:
type: string
format: date
locale:
type: string
theme:
type: string
totp_secret:
type: string
active:
type: boolean
password:
type: string
password2:
type: string
tenant_uuids:
type: array
items:
type: string
format: uuid
primary_tenant_uuid:
type: string
format: uuid
role_uuids:
type: array
items:
type: string
format: uuid
department_uuids:
type: array
items:
type: string
format: uuid
UserUpdateRequest:
type: object
additionalProperties: false
required: [first_name, last_name, email]
properties:
first_name:
type: string
minLength: 1
last_name:
type: string
minLength: 1
email:
type: string
format: email
profile_description:
type: string
job_title:
type: string
phone:
type: string
mobile:
type: string
short_dial:
type: string
address:
type: string
postal_code:
type: string
city:
type: string
country:
type: string
region:
type: string
hire_date:
type: string
format: date
locale:
type: string
theme:
type: string
totp_secret:
type: string
active:
type: boolean
password:
type: string
password2:
type: string
tenant_uuids:
type: array
items:
type: string
format: uuid
primary_tenant_uuid:
type: string
format: uuid
role_uuids:
type: array
items:
type: string
format: uuid
department_uuids:
type: array
items:
type: string
format: uuid
TenantSummary:
type: object
properties:
uuid:
type: string
format: uuid
description:
type: string
status:
type: string
AssignmentDepartment:
type: object
properties:
uuid:
type: string
format: uuid
description:
type: string
active:
type: boolean
tenant_uuid:
type: string
format: uuid
nullable: true
AssignmentRole:
type: object
properties:
uuid:
type: string
format: uuid
description:
type: string
active:
type: boolean
Assignments:
type: object
properties:
tenants:
type: array
items:
$ref: '#/components/schemas/TenantSummary'
departments:
type: array
items:
$ref: '#/components/schemas/AssignmentDepartment'
roles:
type: array
items:
$ref: '#/components/schemas/AssignmentRole'
UserCustomFieldOption:
type: object
properties:
option_key:
type: string
label:
type: string
UserCustomFieldField:
type: object
properties:
uuid:
type: string
format: uuid
field_key:
type: string
label:
type: string
type:
type: string
is_filterable:
type: boolean
value:
description: Type-dependent value (string, boolean, null, or array of option keys).
options:
type: array
items:
$ref: '#/components/schemas/UserCustomFieldOption'
UserCustomFieldTenantGroup:
type: object
properties:
tenant:
$ref: '#/components/schemas/TenantSummary'
fields:
type: array
items:
$ref: '#/components/schemas/UserCustomFieldField'
MeResponse:
type: object
required: [data]
properties:
data:
type: object
properties:
uuid:
type: string
format: uuid
first_name:
type: string
last_name:
type: string
display_name:
type: string
email:
type: string
job_title:
type: string
phone:
type: string
mobile:
type: string
locale:
type: string
current_tenant:
$ref: '#/components/schemas/TenantSummary'
primary_tenant:
$ref: '#/components/schemas/TenantSummary'
can_view_permissions:
type: boolean
permissions:
type: array
items:
type: string
assignments:
$ref: '#/components/schemas/Assignments'
custom_fields:
type: array
items:
$ref: '#/components/schemas/UserCustomFieldTenantGroup'
TokenItem:
type: object
properties:
uuid:
type: string
format: uuid
name:
type: string
selector_prefix:
type: string
tenant_uuid:
type: string
format: uuid
nullable: true
last_used_at:
type: string
nullable: true
last_ip:
type: string
expires_at:
type: string
nullable: true
revoked_at:
type: string
nullable: true
created:
type: string
nullable: true
is_current_token:
type: boolean
TokenListResponse:
type: object
required: [data]
properties:
data:
type: array
items:
$ref: '#/components/schemas/TokenItem'
limit:
type: integer
TokenShowResponse:
type: object
required: [data]
properties:
data:
$ref: '#/components/schemas/TokenItem'
TokenCreateRequest:
type: object
required: [name]
properties:
name:
type: string
tenant_uuid:
type: string
format: uuid
expires_at:
type: string
format: date-time
TokenCreateResponse:
type: object
required: [data]
properties:
data:
type: object
properties:
token_uuid:
type: string
format: uuid
token:
type: string
selector_prefix:
type: string
tenant_uuid:
type: string
format: uuid
nullable: true
expires_at:
type: string
nullable: true
RevokeAllResponse:
type: object
required: [data]
properties:
data:
type: object
properties:
revoked_count:
type: integer
tenant_uuid:
type: string
format: uuid
nullable: true
UserListItem:
type: object
properties:
uuid:
type: string
format: uuid
first_name:
type: string
last_name:
type: string
display_name:
type: string
email:
type: string
job_title:
type: string
phone:
type: string
mobile:
type: string
active:
type: boolean
created:
type: string
tenants:
type: array
items:
type: string
departments:
type: array
items:
type: string
roles:
type: array
items:
type: string
UserListResponse:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/UserListItem'
total:
type: integer
limit:
type: integer
offset:
type: integer
UserShowResponse:
type: object
properties:
data:
type: object
properties:
uuid:
type: string
format: uuid
first_name:
type: string
last_name:
type: string
display_name:
type: string
email:
type: string
profile_description:
type: string
job_title:
type: string
phone:
type: string
mobile:
type: string
short_dial:
type: string
address:
type: string
postal_code:
type: string
city:
type: string
country:
type: string
region:
type: string
hire_date:
type: string
format: date
locale:
type: string
theme:
type: string
email_verified_at:
type: string
nullable: true
last_login_at:
type: string
nullable: true
last_login_provider:
type: string
nullable: true
active:
type: boolean
created:
type: string
modified:
type: string
current_tenant:
$ref: '#/components/schemas/TenantSummary'
primary_tenant:
$ref: '#/components/schemas/TenantSummary'
permissions:
type: array
items:
type: string
assignments:
$ref: '#/components/schemas/Assignments'
custom_fields:
type: array
items:
$ref: '#/components/schemas/UserCustomFieldTenantGroup'
TenantListItem:
type: object
properties:
uuid:
type: string
format: uuid
description:
type: string
status:
type: string
city:
type: string
country:
type: string
created:
type: string
TenantListResponse:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/TenantListItem'
total:
type: integer
limit:
type: integer
offset:
type: integer
TenantShowResponse:
type: object
properties:
data:
type: object
properties:
uuid:
type: string
format: uuid
description:
type: string
status:
type: string
address:
type: string
postal_code:
type: string
city:
type: string
country:
type: string
region:
type: string
vat_id:
type: string
tax_number:
type: string
phone:
type: string
fax:
type: string
email:
type: string
support_email:
type: string
support_phone:
type: string
billing_email:
type: string
website:
type: string
privacy_url:
type: string
imprint_url:
type: string
primary_color:
type: string
primary_color_use_default:
type: boolean
default_theme:
type: string
allow_user_theme_mode:
type: string
enum: ['', '0', '1']
created:
type: string
modified:
type: string
DepartmentListItem:
type: object
properties:
uuid:
type: string
format: uuid
description:
type: string
code:
type: string
active:
type: boolean
tenant_labels:
type: array
items:
type: string
created:
type: string
DepartmentListResponse:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/DepartmentListItem'
total:
type: integer
limit:
type: integer
offset:
type: integer
DepartmentShowResponse:
type: object
properties:
data:
type: object
properties:
uuid:
type: string
format: uuid
description:
type: string
code:
type: string
cost_center:
type: string
active:
type: boolean
tenant_uuid:
type: string
format: uuid
created:
type: string
modified:
type: string
RoleListItem:
type: object
properties:
uuid:
type: string
format: uuid
description:
type: string
code:
type: string
active:
type: boolean
created:
type: string
RoleListResponse:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/RoleListItem'
total:
type: integer
limit:
type: integer
offset:
type: integer
RoleShowResponse:
type: object
properties:
data:
type: object
properties:
uuid:
type: string
format: uuid
description:
type: string
code:
type: string
active:
type: boolean
permission_keys:
type: array
items:
type: string
created:
type: string
modified:
type: string