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 `:`. servers: - url: /api/v1 security: - BearerAuth: [] tags: - name: me - name: tokens - name: users - name: tenants - name: departments - name: roles paths: /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' /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' /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' /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' 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" 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 responses: Unauthorized: description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: unauthorized Forbidden: description: Forbidden content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' NotFound: description: Not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: not_found ValidationError: description: Validation failed content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: validation_error 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: error: rate_limit_exceeded 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: [error] properties: error: type: string errors: type: object additionalProperties: type: array items: type: string GenericDataResponse: type: object required: [data] properties: data: type: object additionalProperties: true 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_id] properties: description: type: string minLength: 1 tenant_id: type: integer minimum: 1 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_ids: type: array items: type: integer minimum: 1 primary_tenant_id: type: integer minimum: 1 role_ids: type: array items: type: integer minimum: 1 department_ids: type: array items: type: integer minimum: 1 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_ids: type: array items: type: integer minimum: 1 primary_tenant_id: type: integer minimum: 1 role_ids: type: array items: type: integer minimum: 1 department_ids: type: array items: type: integer minimum: 1 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 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 active: type: boolean created: type: string modified: type: string 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 phone: type: string email: type: string website: type: string 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 active: type: boolean tenant_id: type: integer nullable: true 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 created: type: string modified: type: string