7351 lines
266 KiB
YAML
7351 lines
266 KiB
YAML
openapi: 3.1.0
|
|
info:
|
|
title: Professional Management Platform API
|
|
version: 1.0.0-milestone.8
|
|
summary: Platform access, engineering design and inspection assurance, and controlled specifications.
|
|
description: |
|
|
Executable API contract for Milestones 1 through 4 of the Professional Management Platform.
|
|
|
|
Tenant-scoped operations require `X-Organization-Id`. Cross-tenant resources are
|
|
reported as not found. Resource creation and material commands require an
|
|
`Idempotency-Key`. Mutable resources use ETags and require `If-Match`.
|
|
|
|
Error responses use RFC 9457 Problem Details extended with stable `code`,
|
|
`requestId`, and optional field-level `errors`.
|
|
contact:
|
|
name: Platform API Team
|
|
servers:
|
|
- url: https://api.example.com/api/v1
|
|
description: Production
|
|
- url: https://sandbox-api.example.com/api/v1
|
|
description: Sandbox
|
|
tags:
|
|
- name: Authentication
|
|
- name: Sessions
|
|
- name: Current User
|
|
- name: Organizations
|
|
- name: Membership Invitations
|
|
- name: Memberships
|
|
- name: Roles
|
|
- name: Permissions
|
|
- name: Engineering Clients
|
|
- name: Engineering Client Contacts
|
|
- name: Engineering Projects
|
|
- name: Engineering Project Members
|
|
- name: Engineering Tasks
|
|
- name: Engineering Sites
|
|
- name: Documents
|
|
- name: Engineering Project Documents
|
|
- name: Engineering Designs
|
|
- name: Engineering Design Assignments
|
|
- name: Engineering Design Versions
|
|
- name: Engineering Design Reviews
|
|
- name: Engineering Inspections
|
|
- name: Engineering Inspection Documents
|
|
- name: Engineering Inspection Findings
|
|
- name: Engineering Inspection Follow-ups
|
|
- name: Engineering Specifications
|
|
- name: Engineering Specification Documents
|
|
|
|
paths:
|
|
/auth/register:
|
|
post:
|
|
tags: [Authentication]
|
|
operationId: registerUser
|
|
summary: Register a user identity
|
|
description: |
|
|
Creates a global user identity. When public registration is disabled, this
|
|
operation returns `REGISTRATION_DISABLED`; invitation acceptance remains
|
|
available to authenticated identities created through the configured onboarding flow.
|
|
security: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RegisterRequest'
|
|
responses:
|
|
'201':
|
|
description: User identity created; email verification may still be required.
|
|
headers:
|
|
Location:
|
|
$ref: '#/components/headers/Location'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UserResponse'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
'422':
|
|
$ref: '#/components/responses/ValidationError'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/auth/login:
|
|
post:
|
|
tags: [Authentication]
|
|
operationId: login
|
|
summary: Authenticate with email and password
|
|
security: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/RequestId'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/LoginRequest'
|
|
responses:
|
|
'200':
|
|
description: Authentication succeeded.
|
|
headers:
|
|
Cache-Control:
|
|
schema:
|
|
type: string
|
|
const: no-store
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/TokenPairResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'422':
|
|
$ref: '#/components/responses/ValidationError'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/auth/logout:
|
|
post:
|
|
tags: [Authentication]
|
|
operationId: logout
|
|
summary: Revoke the current session
|
|
parameters:
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
responses:
|
|
'204':
|
|
description: Current session revoked.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/auth/token/refresh:
|
|
post:
|
|
tags: [Authentication]
|
|
operationId: refreshAccessToken
|
|
summary: Rotate a refresh token and issue a new token pair
|
|
description: Reuse of a rotated refresh token revokes its token family and session.
|
|
security: []
|
|
parameters:
|
|
- $ref: '#/components/parameters/RequestId'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RefreshTokenRequest'
|
|
responses:
|
|
'200':
|
|
description: Token rotated.
|
|
headers:
|
|
Cache-Control:
|
|
schema:
|
|
type: string
|
|
const: no-store
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/TokenPairResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/auth/token/revoke:
|
|
post:
|
|
tags: [Authentication]
|
|
operationId: revokeRefreshToken
|
|
summary: Revoke one refresh-token family
|
|
parameters:
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RefreshTokenRequest'
|
|
responses:
|
|
'204':
|
|
description: Token family revoked or already revoked.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/auth/token/revoke-all:
|
|
post:
|
|
tags: [Authentication]
|
|
operationId: revokeAllSessions
|
|
summary: Revoke all sessions for the current user
|
|
parameters:
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
responses:
|
|
'204':
|
|
description: All sessions revoked, including the current session.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/auth/sessions:
|
|
get:
|
|
tags: [Sessions]
|
|
operationId: listSessions
|
|
summary: List sessions for the current user
|
|
parameters:
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
responses:
|
|
'200':
|
|
description: Sessions returned.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/SessionCollectionResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/auth/sessions/{sessionId}:
|
|
delete:
|
|
tags: [Sessions]
|
|
operationId: revokeSession
|
|
summary: Revoke a specific session
|
|
parameters:
|
|
- $ref: '#/components/parameters/SessionId'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
responses:
|
|
'204':
|
|
description: Session revoked or already revoked.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/me:
|
|
get:
|
|
tags: [Current User]
|
|
operationId: getCurrentUser
|
|
summary: Get the current user
|
|
parameters:
|
|
- $ref: '#/components/parameters/RequestId'
|
|
responses:
|
|
'200':
|
|
description: Current user returned.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UserResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
patch:
|
|
tags: [Current User]
|
|
operationId: updateCurrentUser
|
|
summary: Update the current user's profile
|
|
parameters:
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/merge-patch+json:
|
|
schema:
|
|
$ref: '#/components/schemas/UpdateCurrentUserRequest'
|
|
responses:
|
|
'200':
|
|
description: Current user updated.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/UserResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
'422':
|
|
$ref: '#/components/responses/ValidationError'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/me/organizations:
|
|
get:
|
|
tags: [Current User]
|
|
operationId: listCurrentUserOrganizations
|
|
summary: List organizations accessible to the current user
|
|
parameters:
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
responses:
|
|
'200':
|
|
description: Accessible organizations returned.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/OrganizationCollectionResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/organizations:
|
|
post:
|
|
tags: [Organizations]
|
|
operationId: createOrganization
|
|
summary: Create an organization
|
|
x-authorization-policy: authenticated_user_may_create_organization
|
|
x-audit-action: organizations.create
|
|
description: |
|
|
Atomically creates the organization, enables its initial profession modules,
|
|
creates an active owner membership, assigns the immutable Owner system role,
|
|
and writes audit and outbox records.
|
|
parameters:
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateOrganizationRequest'
|
|
responses:
|
|
'201':
|
|
description: Organization created.
|
|
headers:
|
|
Location:
|
|
$ref: '#/components/headers/Location'
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/OrganizationResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
'422':
|
|
$ref: '#/components/responses/ValidationError'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/organizations/{organizationId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
get:
|
|
tags: [Organizations]
|
|
operationId: getOrganization
|
|
summary: Get an organization
|
|
x-required-permissions: [organizations.read]
|
|
responses:
|
|
'200':
|
|
description: Organization returned.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/OrganizationResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
patch:
|
|
tags: [Organizations]
|
|
operationId: updateOrganization
|
|
summary: Update organization settings
|
|
x-required-permissions: [organizations.update]
|
|
x-audit-action: organizations.update
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/merge-patch+json:
|
|
schema:
|
|
$ref: '#/components/schemas/UpdateOrganizationRequest'
|
|
responses:
|
|
'200':
|
|
description: Organization updated.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/OrganizationResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/membership-invitations:
|
|
get:
|
|
tags: [Membership Invitations]
|
|
operationId: listMembershipInvitations
|
|
summary: List membership invitations
|
|
x-required-permissions: [members.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/InvitationStatus'
|
|
responses:
|
|
'200':
|
|
description: Invitations returned.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/InvitationCollectionResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
post:
|
|
tags: [Membership Invitations]
|
|
operationId: createMembershipInvitation
|
|
summary: Invite a person to the current organization
|
|
x-required-permissions: [members.invite]
|
|
x-audit-action: memberships.invite
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateInvitationRequest'
|
|
responses:
|
|
'201':
|
|
description: Invitation created.
|
|
headers:
|
|
Location:
|
|
$ref: '#/components/headers/Location'
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/InvitationResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
|
|
/membership-invitations/accept:
|
|
post:
|
|
tags: [Membership Invitations]
|
|
operationId: acceptMembershipInvitation
|
|
summary: Accept an invitation for the current user
|
|
x-authorization-policy: invitation_email_must_match_current_user
|
|
x-audit-action: memberships.accept_invitation
|
|
description: |
|
|
The invitation token is sent in the request body to avoid path and access-log
|
|
disclosure. Acceptance atomically creates the membership, copies valid intended
|
|
roles, marks the invitation accepted, and writes audit and outbox records.
|
|
parameters:
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AcceptInvitationRequest'
|
|
responses:
|
|
'201':
|
|
description: Invitation accepted and membership created.
|
|
headers:
|
|
Location:
|
|
$ref: '#/components/headers/Location'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/MembershipResponse'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
'422':
|
|
$ref: '#/components/responses/ValidationError'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/membership-invitations/{invitationId}:
|
|
get:
|
|
tags: [Membership Invitations]
|
|
operationId: getMembershipInvitation
|
|
summary: Get a membership invitation
|
|
x-required-permissions: [members.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/InvitationId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
responses:
|
|
'200':
|
|
description: Invitation returned.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/InvitationResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/membership-invitations/{invitationId}/revoke:
|
|
post:
|
|
tags: [Membership Invitations]
|
|
operationId: revokeMembershipInvitation
|
|
summary: Revoke a pending invitation
|
|
x-required-permissions: [members.invite]
|
|
x-audit-action: memberships.revoke_invitation
|
|
parameters:
|
|
- $ref: '#/components/parameters/InvitationId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
responses:
|
|
'200':
|
|
description: Invitation revoked or already revoked.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/InvitationResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/membership-invitations/{invitationId}/resend:
|
|
post:
|
|
tags: [Membership Invitations]
|
|
operationId: resendMembershipInvitation
|
|
summary: Rotate the token and resend a pending invitation
|
|
x-required-permissions: [members.invite]
|
|
x-audit-action: memberships.resend_invitation
|
|
parameters:
|
|
- $ref: '#/components/parameters/InvitationId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
responses:
|
|
'200':
|
|
description: Invitation token rotated and delivery queued.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/InvitationResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/memberships:
|
|
get:
|
|
tags: [Memberships]
|
|
operationId: listMemberships
|
|
summary: List memberships in the current organization
|
|
x-required-permissions: [members.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/MembershipStatus'
|
|
- name: userId
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
responses:
|
|
'200':
|
|
description: Memberships returned.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/MembershipCollectionResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/memberships/{membershipId}:
|
|
get:
|
|
tags: [Memberships]
|
|
operationId: getMembership
|
|
summary: Get a membership
|
|
x-required-permissions: [members.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/MembershipId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
responses:
|
|
'200':
|
|
description: Membership returned.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/MembershipResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/memberships/{membershipId}/deactivate:
|
|
post:
|
|
tags: [Memberships]
|
|
operationId: deactivateMembership
|
|
summary: Deactivate a membership
|
|
description: |
|
|
Rejected when the member is the last active organization Owner or manages any
|
|
active engineering project, has active project participation, or is assigned open
|
|
engineering tasks. Those responsibilities must be reassigned or ended first.
|
|
x-required-permissions: [members.update]
|
|
x-audit-action: memberships.deactivate
|
|
parameters:
|
|
- $ref: '#/components/parameters/MembershipId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ReasonRequest'
|
|
responses:
|
|
'200':
|
|
description: Membership deactivated or already inactive.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/MembershipResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/memberships/{membershipId}/reactivate:
|
|
post:
|
|
tags: [Memberships]
|
|
operationId: reactivateMembership
|
|
summary: Reactivate an inactive membership
|
|
x-required-permissions: [members.update]
|
|
x-audit-action: memberships.reactivate
|
|
parameters:
|
|
- $ref: '#/components/parameters/MembershipId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
responses:
|
|
'200':
|
|
description: Membership reactivated or already active.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/MembershipResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/memberships/{membershipId}/roles:
|
|
put:
|
|
tags: [Memberships, Roles]
|
|
operationId: replaceMembershipRoles
|
|
summary: Replace all roles assigned to a membership
|
|
x-required-permissions: [roles.manage]
|
|
x-audit-action: memberships.replace_roles
|
|
description: |
|
|
The replacement is atomic. Every supplied role must belong to the current
|
|
organization. The operation rejects removal of the last active Owner.
|
|
parameters:
|
|
- $ref: '#/components/parameters/MembershipId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ReplaceMembershipRolesRequest'
|
|
responses:
|
|
'200':
|
|
description: Membership roles replaced.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/MembershipResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/roles:
|
|
get:
|
|
tags: [Roles]
|
|
operationId: listRoles
|
|
summary: List roles in the current organization
|
|
x-required-permissions: [roles.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
responses:
|
|
'200':
|
|
description: Roles returned.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RoleCollectionResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
post:
|
|
tags: [Roles]
|
|
operationId: createRole
|
|
summary: Create a custom role
|
|
x-required-permissions: [roles.manage]
|
|
x-audit-action: roles.create
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateRoleRequest'
|
|
responses:
|
|
'201':
|
|
description: Role created.
|
|
headers:
|
|
Location:
|
|
$ref: '#/components/headers/Location'
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RoleResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
|
|
/roles/{roleId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/RoleId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
get:
|
|
tags: [Roles]
|
|
operationId: getRole
|
|
summary: Get a role
|
|
x-required-permissions: [roles.read]
|
|
responses:
|
|
'200':
|
|
description: Role returned.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RoleResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
patch:
|
|
tags: [Roles]
|
|
operationId: updateRole
|
|
summary: Update a custom role
|
|
x-required-permissions: [roles.manage]
|
|
x-audit-action: roles.update
|
|
description: Immutable system roles cannot be modified.
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/merge-patch+json:
|
|
schema:
|
|
$ref: '#/components/schemas/UpdateRoleRequest'
|
|
responses:
|
|
'200':
|
|
description: Role updated.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RoleResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/roles/{roleId}/deactivate:
|
|
post:
|
|
tags: [Roles]
|
|
operationId: deactivateRole
|
|
summary: Deactivate a custom role
|
|
x-required-permissions: [roles.manage]
|
|
x-audit-action: roles.deactivate
|
|
description: |
|
|
Prevents future assignment of the role without deleting historical assignments.
|
|
Immutable system roles cannot be deactivated.
|
|
parameters:
|
|
- $ref: '#/components/parameters/RoleId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
responses:
|
|
'200':
|
|
description: Role deactivated or already inactive.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RoleResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/roles/{roleId}/reactivate:
|
|
post:
|
|
tags: [Roles]
|
|
operationId: reactivateRole
|
|
summary: Reactivate an inactive custom role
|
|
x-required-permissions: [roles.manage]
|
|
x-audit-action: roles.reactivate
|
|
parameters:
|
|
- $ref: '#/components/parameters/RoleId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
responses:
|
|
'200':
|
|
description: Role reactivated or already active.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RoleResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/permissions:
|
|
get:
|
|
tags: [Permissions]
|
|
operationId: listPermissions
|
|
summary: List registered permissions available to the organization
|
|
x-required-permissions: [roles.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: profession
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/Profession'
|
|
responses:
|
|
'200':
|
|
description: Permissions returned.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/PermissionCollectionResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/clients:
|
|
get:
|
|
tags: [Engineering Clients]
|
|
operationId: listEngineeringClients
|
|
summary: List engineering clients
|
|
description: Archived clients are excluded unless `status=archived` is requested explicitly.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.clients.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: clientType
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringClientType'
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringClientStatus'
|
|
- name: q
|
|
in: query
|
|
description: Case-insensitive search across display name and legal name.
|
|
schema:
|
|
type: string
|
|
minLength: 2
|
|
maxLength: 200
|
|
- name: sort
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [displayName, -displayName, createdAt, -createdAt]
|
|
default: displayName
|
|
responses:
|
|
'200':
|
|
description: Engineering clients returned.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringClientCollectionResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'422':
|
|
$ref: '#/components/responses/ValidationError'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
post:
|
|
tags: [Engineering Clients]
|
|
operationId: createEngineeringClient
|
|
summary: Create an engineering client
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.clients.create]
|
|
x-audit-action: engineering.clients.create
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateEngineeringClientRequest'
|
|
responses:
|
|
'201':
|
|
description: Engineering client created.
|
|
headers:
|
|
Location:
|
|
$ref: '#/components/headers/Location'
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringClientResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
|
|
/engineering/clients/{clientId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/ClientId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
get:
|
|
tags: [Engineering Clients]
|
|
operationId: getEngineeringClient
|
|
summary: Get an engineering client
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.clients.read]
|
|
responses:
|
|
'200':
|
|
description: Engineering client returned.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringClientResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
patch:
|
|
tags: [Engineering Clients]
|
|
operationId: updateEngineeringClient
|
|
summary: Update an active engineering client
|
|
description: Status changes are not accepted here; use archive and restore commands.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.clients.update]
|
|
x-audit-action: engineering.clients.update
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/merge-patch+json:
|
|
schema:
|
|
$ref: '#/components/schemas/UpdateEngineeringClientRequest'
|
|
responses:
|
|
'200':
|
|
description: Engineering client updated.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringClientResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/clients/{clientId}/archive:
|
|
post:
|
|
tags: [Engineering Clients]
|
|
operationId: archiveEngineeringClient
|
|
summary: Archive an engineering client
|
|
description: |
|
|
Archiving removes the client from default active lists without deleting client,
|
|
contact, project, billing, audit, or document history. The command is rejected
|
|
while the client has any project in `draft` or `active` status.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.clients.archive]
|
|
x-audit-action: engineering.clients.archive
|
|
parameters:
|
|
- $ref: '#/components/parameters/ClientId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ReasonRequest'
|
|
responses:
|
|
'200':
|
|
description: Engineering client archived or already archived.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringClientResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/clients/{clientId}/restore:
|
|
post:
|
|
tags: [Engineering Clients]
|
|
operationId: restoreEngineeringClient
|
|
summary: Restore an archived engineering client
|
|
description: Restore is rejected when organization policy or retention rules prohibit it.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.clients.archive]
|
|
x-audit-action: engineering.clients.restore
|
|
parameters:
|
|
- $ref: '#/components/parameters/ClientId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
responses:
|
|
'200':
|
|
description: Engineering client restored or already active.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringClientResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/clients/{clientId}/projects:
|
|
get:
|
|
tags: [Engineering Clients]
|
|
operationId: listEngineeringClientProjects
|
|
summary: List projects belonging to an engineering client
|
|
description: This is a client-scoped projection; full project representations arrive in Milestone 3.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.clients.read, engineering.projects.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/ClientId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectStatus'
|
|
- name: sort
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [projectNumber, -projectNumber, createdAt, -createdAt]
|
|
default: -createdAt
|
|
responses:
|
|
'200':
|
|
description: Client projects returned.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectSummaryCollectionResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
|
|
/engineering/clients/{clientId}/contacts:
|
|
get:
|
|
tags: [Engineering Client Contacts]
|
|
operationId: listEngineeringClientContacts
|
|
summary: List contacts for an engineering client
|
|
description: Archived contacts are excluded unless `status=archived` is requested explicitly.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.clients.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/ClientId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: contactType
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringContactType'
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringContactStatus'
|
|
- name: isPrimary
|
|
in: query
|
|
schema:
|
|
type: boolean
|
|
- name: sort
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [name, -name, createdAt, -createdAt]
|
|
default: name
|
|
responses:
|
|
'200':
|
|
description: Client contacts returned.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringClientContactCollectionResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
post:
|
|
tags: [Engineering Client Contacts]
|
|
operationId: createEngineeringClientContact
|
|
summary: Create a contact for an engineering client
|
|
description: |
|
|
When `isPrimary=true`, any current primary contact of the same contact type
|
|
is demoted atomically in the same transaction.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.clients.update]
|
|
x-audit-action: engineering.client_contacts.create
|
|
parameters:
|
|
- $ref: '#/components/parameters/ClientId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateEngineeringClientContactRequest'
|
|
responses:
|
|
'201':
|
|
description: Client contact created.
|
|
headers:
|
|
Location:
|
|
$ref: '#/components/headers/Location'
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringClientContactResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
|
|
/engineering/clients/{clientId}/contacts/{contactId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/ClientId'
|
|
- $ref: '#/components/parameters/ContactId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
get:
|
|
tags: [Engineering Client Contacts]
|
|
operationId: getEngineeringClientContact
|
|
summary: Get an engineering client contact
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.clients.read]
|
|
responses:
|
|
'200':
|
|
description: Client contact returned.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringClientContactResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
patch:
|
|
tags: [Engineering Client Contacts]
|
|
operationId: updateEngineeringClientContact
|
|
summary: Update an active engineering client contact
|
|
description: |
|
|
When `isPrimary=true`, any current primary contact of the resulting contact
|
|
type is demoted atomically. Status is not patchable.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.clients.update]
|
|
x-audit-action: engineering.client_contacts.update
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/merge-patch+json:
|
|
schema:
|
|
$ref: '#/components/schemas/UpdateEngineeringClientContactRequest'
|
|
responses:
|
|
'200':
|
|
description: Client contact updated.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringClientContactResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
delete:
|
|
tags: [Engineering Client Contacts]
|
|
operationId: archiveEngineeringClientContact
|
|
summary: Archive an engineering client contact
|
|
description: |
|
|
This operation is a recoverable logical archive, not a physical delete. Historical
|
|
references remain intact. Archiving a primary contact clears its primary flag.
|
|
Repeating the operation for an archived contact returns 204.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.clients.update]
|
|
x-audit-action: engineering.client_contacts.archive
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
responses:
|
|
'204':
|
|
description: Client contact archived or already archived.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/clients/{clientId}/contacts/{contactId}/restore:
|
|
post:
|
|
tags: [Engineering Client Contacts]
|
|
operationId: restoreEngineeringClientContact
|
|
summary: Restore an archived engineering client contact
|
|
description: The parent client must be active. Restored contacts are not primary by default.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.clients.update]
|
|
x-audit-action: engineering.client_contacts.restore
|
|
parameters:
|
|
- $ref: '#/components/parameters/ClientId'
|
|
- $ref: '#/components/parameters/ContactId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
responses:
|
|
'200':
|
|
description: Client contact restored or already active.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringClientContactResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/projects:
|
|
get:
|
|
tags: [Engineering Projects]
|
|
operationId: listEngineeringProjects
|
|
summary: List engineering projects
|
|
description: |
|
|
Archived projects are excluded unless `status=archived` is requested explicitly.
|
|
Permission scope is enforced in the query: `assigned` resolves through active project
|
|
membership or the project-manager pointer; `organization` resolves across the tenant.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.projects.read]
|
|
x-permission-scope-policy: engineering_project_assignment
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: clientId
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectStatus'
|
|
- name: discipline
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringDiscipline'
|
|
- name: projectManagerUserId
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
- name: q
|
|
in: query
|
|
description: Case-insensitive search across project number, project name, and client name.
|
|
schema:
|
|
type: string
|
|
minLength: 2
|
|
maxLength: 200
|
|
- name: sort
|
|
in: query
|
|
description: Supported deterministic sort. Null date values are always placed last.
|
|
schema:
|
|
type: string
|
|
enum:
|
|
- projectNumber
|
|
- -projectNumber
|
|
- name
|
|
- -name
|
|
- startDate
|
|
- -startDate
|
|
- expectedCompletionDate
|
|
- -expectedCompletionDate
|
|
- createdAt
|
|
- -createdAt
|
|
default: -createdAt
|
|
responses:
|
|
'200':
|
|
description: Engineering projects returned.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectCollectionResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'422':
|
|
$ref: '#/components/responses/ValidationError'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
post:
|
|
tags: [Engineering Projects]
|
|
operationId: createEngineeringProject
|
|
summary: Create an engineering project in draft status
|
|
description: |
|
|
`projectNumber` is immutable and unique case-insensitively within the organization.
|
|
The referenced client must be active. A supplied project manager must have an active
|
|
membership in the same organization. `projectManagerUserId` is the sole project-manager
|
|
authority and is not duplicated as a project-member role.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.projects.create]
|
|
x-audit-action: engineering.projects.create
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateEngineeringProjectRequest'
|
|
responses:
|
|
'201':
|
|
description: Engineering project created.
|
|
headers:
|
|
Location:
|
|
$ref: '#/components/headers/Location'
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
|
|
/engineering/projects/{projectId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
get:
|
|
tags: [Engineering Projects]
|
|
operationId: getEngineeringProject
|
|
summary: Get an engineering project
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.projects.read]
|
|
x-permission-scope-policy: engineering_project_assignment
|
|
responses:
|
|
'200':
|
|
description: Engineering project returned.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
patch:
|
|
tags: [Engineering Projects]
|
|
operationId: updateEngineeringProject
|
|
summary: Update editable engineering project fields
|
|
description: |
|
|
`projectNumber`, `status`, completion fields, and archive fields are not patchable.
|
|
`clientId` may change only while the project is `draft` and has no dependent records.
|
|
Changing `projectManagerUserId` changes assigned-scope access and is audited. It does
|
|
not create a duplicate `project_manager` project-member role. Open tasks assigned to
|
|
the outgoing manager must first be reassigned unless that user remains an active member.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.projects.update]
|
|
x-audit-action: engineering.projects.update
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/merge-patch+json:
|
|
schema:
|
|
$ref: '#/components/schemas/UpdateEngineeringProjectRequest'
|
|
responses:
|
|
'200':
|
|
description: Engineering project updated.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/projects/{projectId}/activate:
|
|
post:
|
|
tags: [Engineering Projects]
|
|
operationId: activateEngineeringProject
|
|
summary: Activate a draft engineering project
|
|
description: |
|
|
Transition: `draft → active`. The client and project manager must both be active.
|
|
When `startDate` is absent from both the project and request, the server uses the
|
|
current date in the organization's configured time zone.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.projects.activate]
|
|
x-audit-action: engineering.projects.activate
|
|
parameters:
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ActivateEngineeringProjectRequest'
|
|
responses:
|
|
'200':
|
|
description: Engineering project activated or idempotent result replayed.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/projects/{projectId}/close:
|
|
post:
|
|
tags: [Engineering Projects]
|
|
operationId: closeEngineeringProject
|
|
summary: Close an active engineering project
|
|
description: |
|
|
Transition: `active → closed`. When `completedDate` is omitted, the server uses
|
|
the current date in the organization's configured time zone. The completed date
|
|
cannot precede the project start date. Every task must already be `completed` or
|
|
`cancelled`.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.projects.close]
|
|
x-audit-action: engineering.projects.close
|
|
parameters:
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CloseEngineeringProjectRequest'
|
|
responses:
|
|
'200':
|
|
description: Engineering project closed or idempotent result replayed.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/projects/{projectId}/archive:
|
|
post:
|
|
tags: [Engineering Projects]
|
|
operationId: archiveEngineeringProject
|
|
summary: Archive a draft or closed engineering project
|
|
description: |
|
|
Transition: `draft|closed → archived`. Active projects must be closed first.
|
|
The prior status is retained so restore is deterministic. Related records and
|
|
audit history are never physically deleted. Every task must already be `completed`
|
|
or `cancelled`.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.projects.archive]
|
|
x-audit-action: engineering.projects.archive
|
|
parameters:
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ReasonRequest'
|
|
responses:
|
|
'200':
|
|
description: Engineering project archived or idempotent result replayed.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/projects/{projectId}/restore:
|
|
post:
|
|
tags: [Engineering Projects]
|
|
operationId: restoreEngineeringProject
|
|
summary: Restore an archived engineering project
|
|
description: |
|
|
Transition: `archived → archivedFromStatus`, which is either `draft` or `closed`.
|
|
Restore never reactivates a project implicitly. The referenced client must be active.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.projects.archive]
|
|
x-audit-action: engineering.projects.restore
|
|
parameters:
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
responses:
|
|
'200':
|
|
description: Engineering project restored or idempotent result replayed.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/projects/{projectId}/summary:
|
|
get:
|
|
tags: [Engineering Projects]
|
|
operationId: getEngineeringProjectSummary
|
|
summary: Get the engineering project dashboard summary
|
|
description: |
|
|
Returns a purpose-built read model. Counts are permission-filtered and include
|
|
only records visible to the caller. Modules not yet enabled return zero counts,
|
|
not omitted fields, preserving the response shape.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.projects.read]
|
|
x-permission-scope-policy: engineering_project_assignment
|
|
parameters:
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
responses:
|
|
'200':
|
|
description: Project summary returned.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectDashboardResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/projects/{projectId}/members:
|
|
get:
|
|
tags: [Engineering Project Members]
|
|
operationId: listEngineeringProjectMembers
|
|
summary: List temporal project-member records
|
|
description: By default, only active participation records are returned.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.projects.read]
|
|
x-permission-scope-policy: engineering_project_assignment
|
|
parameters:
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectMemberStatus'
|
|
- name: projectRole
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectMemberRole'
|
|
- name: userId
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
- name: sort
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [joinedAt, -joinedAt, name, -name]
|
|
default: name
|
|
responses:
|
|
'200':
|
|
description: Project-member records returned.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectMemberCollectionResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
post:
|
|
tags: [Engineering Project Members]
|
|
operationId: addEngineeringProjectMember
|
|
summary: Add an active organization member to a project
|
|
description: |
|
|
The project must be `draft` or `active`. The user must have an active organization
|
|
membership. Rejoining after departure creates a new temporal row. Only one active
|
|
row may exist for a user in a project. Project-manager assignment is controlled by
|
|
`projectManagerUserId`, not by this endpoint.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.project_members.manage]
|
|
x-audit-action: engineering.project_members.add
|
|
parameters:
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateEngineeringProjectMemberRequest'
|
|
responses:
|
|
'201':
|
|
description: Project member added.
|
|
headers:
|
|
Location:
|
|
$ref: '#/components/headers/Location'
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectMemberResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
|
|
/engineering/projects/{projectId}/members/{memberId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
- $ref: '#/components/parameters/ProjectMemberId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
get:
|
|
tags: [Engineering Project Members]
|
|
operationId: getEngineeringProjectMember
|
|
summary: Get a project-member record
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.projects.read]
|
|
x-permission-scope-policy: engineering_project_assignment
|
|
responses:
|
|
'200':
|
|
description: Project-member record returned.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectMemberResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
patch:
|
|
tags: [Engineering Project Members]
|
|
operationId: updateEngineeringProjectMember
|
|
summary: Change the participation role of an active project member
|
|
description: Only `projectRole` is patchable in v1.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.project_members.manage]
|
|
x-audit-action: engineering.project_members.update
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/merge-patch+json:
|
|
schema:
|
|
$ref: '#/components/schemas/UpdateEngineeringProjectMemberRequest'
|
|
responses:
|
|
'200':
|
|
description: Participation role updated.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringProjectMemberResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
delete:
|
|
tags: [Engineering Project Members]
|
|
operationId: endEngineeringProjectMembership
|
|
summary: End a user's project participation
|
|
description: |
|
|
Sets `leftAt`; it never deletes history. Repeating the command with the same
|
|
idempotency key replays the original 204 response. Open tasks assigned to the
|
|
user must be reassigned or unassigned first.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.project_members.manage]
|
|
x-audit-action: engineering.project_members.remove
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
responses:
|
|
'204':
|
|
description: Project participation ended or idempotent result replayed.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/tasks:
|
|
get:
|
|
tags: [Engineering Tasks]
|
|
operationId: listEngineeringTasks
|
|
summary: List engineering tasks
|
|
description: |
|
|
Permission scope is enforced per task. Assigned scope resolves when the caller is
|
|
the task assignee, an active member of the parent project, or its project manager.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.tasks.manage]
|
|
x-permission-scope-policy: engineering_task_or_parent_project_assignment
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: projectId
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
- name: status
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringTaskStatus'
|
|
- name: priority
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringTaskPriority'
|
|
- name: assignedToUserId
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
- name: assignmentStatus
|
|
in: query
|
|
schema:
|
|
type: string
|
|
enum: [assigned, unassigned, any]
|
|
default: any
|
|
- name: dueBefore
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
- name: dueAfter
|
|
in: query
|
|
schema:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
- name: q
|
|
in: query
|
|
description: Case-insensitive search across task title and description.
|
|
schema:
|
|
type: string
|
|
minLength: 2
|
|
maxLength: 200
|
|
- name: sort
|
|
in: query
|
|
description: Null due dates are always placed last.
|
|
schema:
|
|
type: string
|
|
enum: [createdAt, -createdAt, dueAt, -dueAt, priority, -priority]
|
|
default: -createdAt
|
|
responses:
|
|
'200':
|
|
description: Engineering tasks returned.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringTaskCollectionResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'422':
|
|
$ref: '#/components/responses/ValidationError'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
post:
|
|
tags: [Engineering Tasks]
|
|
operationId: createEngineeringTask
|
|
summary: Create a task in todo status
|
|
description: |
|
|
The project must be `draft` or `active`. A supplied assignee must be the project
|
|
manager or an active project member and must retain an active organization membership.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.tasks.manage]
|
|
x-audit-action: engineering.tasks.create
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CreateEngineeringTaskRequest'
|
|
responses:
|
|
'201':
|
|
description: Engineering task created.
|
|
headers:
|
|
Location:
|
|
$ref: '#/components/headers/Location'
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringTaskResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
|
|
/engineering/tasks/{taskId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/TaskId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
get:
|
|
tags: [Engineering Tasks]
|
|
operationId: getEngineeringTask
|
|
summary: Get an engineering task
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.tasks.manage]
|
|
x-permission-scope-policy: engineering_task_or_parent_project_assignment
|
|
responses:
|
|
'200':
|
|
description: Engineering task returned.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringTaskResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
patch:
|
|
tags: [Engineering Tasks]
|
|
operationId: updateEngineeringTask
|
|
summary: Update mutable task fields
|
|
description: |
|
|
`projectId`, status, creator, and terminal metadata are immutable through PATCH.
|
|
Assignment changes revalidate active organization and project participation.
|
|
Completed and cancelled tasks must be reopened before they can be edited. The parent
|
|
project must be `draft` or `active`.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.tasks.manage]
|
|
x-permission-scope-policy: engineering_task_or_parent_project_assignment
|
|
x-audit-action: engineering.tasks.update
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/merge-patch+json:
|
|
schema:
|
|
$ref: '#/components/schemas/UpdateEngineeringTaskRequest'
|
|
responses:
|
|
'200':
|
|
description: Engineering task updated.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringTaskResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/tasks/{taskId}/start:
|
|
post:
|
|
tags: [Engineering Tasks]
|
|
operationId: startEngineeringTask
|
|
summary: Start a todo task
|
|
description: 'Transition: `todo → in_progress`; the parent project must be `draft` or `active`.'
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.tasks.manage]
|
|
x-permission-scope-policy: engineering_task_or_parent_project_assignment
|
|
x-audit-action: engineering.tasks.start
|
|
parameters:
|
|
- $ref: '#/components/parameters/TaskId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
responses:
|
|
'200':
|
|
description: Engineering task started or idempotent result replayed.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringTaskResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'401':
|
|
$ref: '#/components/responses/Unauthorized'
|
|
'403':
|
|
$ref: '#/components/responses/Forbidden'
|
|
'404':
|
|
$ref: '#/components/responses/NotFound'
|
|
'409':
|
|
$ref: '#/components/responses/Conflict'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/tasks/{taskId}/complete:
|
|
post:
|
|
tags: [Engineering Tasks]
|
|
operationId: completeEngineeringTask
|
|
summary: Complete a todo or in-progress task
|
|
description: 'Transition: `todo|in_progress → completed`; the parent project must be `draft` or `active`.'
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.tasks.manage]
|
|
x-permission-scope-policy: engineering_task_or_parent_project_assignment
|
|
x-audit-action: engineering.tasks.complete
|
|
parameters:
|
|
- $ref: '#/components/parameters/TaskId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CompleteEngineeringTaskRequest'
|
|
responses:
|
|
'200':
|
|
description: Engineering task completed or idempotent result replayed.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringTaskResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/tasks/{taskId}/reopen:
|
|
post:
|
|
tags: [Engineering Tasks]
|
|
operationId: reopenEngineeringTask
|
|
summary: Reopen a completed or cancelled task
|
|
description: |
|
|
Transition: `completed|cancelled → todo`. Completion and cancellation metadata
|
|
plus any prior start metadata are cleared, while their prior values remain available
|
|
through audit history. The parent project must be `draft` or `active`.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.tasks.manage]
|
|
x-permission-scope-policy: engineering_task_or_parent_project_assignment
|
|
x-audit-action: engineering.tasks.reopen
|
|
parameters:
|
|
- $ref: '#/components/parameters/TaskId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ReasonRequest'
|
|
responses:
|
|
'200':
|
|
description: Engineering task reopened or idempotent result replayed.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringTaskResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/tasks/{taskId}/cancel:
|
|
post:
|
|
tags: [Engineering Tasks]
|
|
operationId: cancelEngineeringTask
|
|
summary: Cancel a todo or in-progress task
|
|
description: 'Transition: `todo|in_progress → cancelled`; the parent project must be `draft` or `active`.'
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.tasks.manage]
|
|
x-permission-scope-policy: engineering_task_or_parent_project_assignment
|
|
x-audit-action: engineering.tasks.cancel
|
|
parameters:
|
|
- $ref: '#/components/parameters/TaskId'
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/CancelEngineeringTaskRequest'
|
|
responses:
|
|
'200':
|
|
description: Engineering task cancelled or idempotent result replayed.
|
|
headers:
|
|
ETag:
|
|
$ref: '#/components/headers/ETag'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringTaskResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
'428':
|
|
$ref: '#/components/responses/PreconditionRequired'
|
|
'429':
|
|
$ref: '#/components/responses/RateLimited'
|
|
|
|
/engineering/tasks/batch/assign:
|
|
post:
|
|
tags: [Engineering Tasks]
|
|
operationId: batchAssignEngineeringTasks
|
|
summary: Assign multiple tasks
|
|
description: |
|
|
Every item carries its expected version and is independently tenant-, permission-,
|
|
scope-, project-, assignee-, and state-validated. Atomic mode rolls back all items
|
|
on any failure. Partial mode commits valid items and returns per-item failures.
|
|
Only `todo` and `in_progress` tasks may be assigned, and the assignee must be an
|
|
active participant or project manager for every affected project.
|
|
Milestone 4 executes at most 100 items synchronously; larger requests are rejected.
|
|
Asynchronous execution is introduced with the background-jobs milestone.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.tasks.manage]
|
|
x-audit-action: engineering.tasks.batch_assign
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/BatchAssignEngineeringTasksRequest'
|
|
responses:
|
|
'200':
|
|
description: Batch executed synchronously.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringTaskBatchResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
|
|
/engineering/tasks/batch/complete:
|
|
post:
|
|
tags: [Engineering Tasks]
|
|
operationId: batchCompleteEngineeringTasks
|
|
summary: Complete multiple tasks
|
|
description: |
|
|
Every item carries its expected version and is independently authorized and
|
|
state-validated. Atomic and partial modes follow the same semantics as batch assign.
|
|
Only `todo` and `in_progress` tasks may be completed.
|
|
Milestone 4 executes at most 100 items synchronously; larger requests are rejected.
|
|
Asynchronous execution is introduced with the background-jobs milestone.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.tasks.manage]
|
|
x-audit-action: engineering.tasks.batch_complete
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/BatchCompleteEngineeringTasksRequest'
|
|
responses:
|
|
'200':
|
|
description: Batch executed synchronously.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/EngineeringTaskBatchResponse'
|
|
'400':
|
|
$ref: '#/components/responses/BadRequest'
|
|
'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'
|
|
|
|
/engineering/sites:
|
|
get:
|
|
tags: [Engineering Sites]
|
|
operationId: listEngineeringSites
|
|
summary: List engineering sites across the active organization
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.sites.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: projectId
|
|
in: query
|
|
schema: {$ref: '#/components/schemas/Uuid'}
|
|
- name: search
|
|
in: query
|
|
schema: {type: string, minLength: 1, maxLength: 200}
|
|
responses:
|
|
'200':
|
|
description: Sites visible to the caller.
|
|
headers: {X-Request-Id: {$ref: '#/components/headers/RequestId'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringSiteCollectionResponse'}}}
|
|
'401': {$ref: '#/components/responses/Unauthorized'}
|
|
'403': {$ref: '#/components/responses/Forbidden'}
|
|
|
|
/engineering/projects/{projectId}/sites:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
get:
|
|
tags: [Engineering Sites]
|
|
operationId: listEngineeringProjectSites
|
|
summary: List sites for one project
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.sites.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
responses:
|
|
'200':
|
|
description: Project sites.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringSiteCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Engineering Sites]
|
|
operationId: createEngineeringProjectSite
|
|
summary: Create a site within a project
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.sites.manage]
|
|
x-audit-action: engineering.site.created
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/CreateEngineeringSiteRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Site created.
|
|
headers: {Location: {$ref: '#/components/headers/Location'}, ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringSiteResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/sites/{siteId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/SiteId'
|
|
get:
|
|
tags: [Engineering Sites]
|
|
operationId: getEngineeringSite
|
|
summary: Retrieve an engineering site
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.sites.read]
|
|
responses:
|
|
'200':
|
|
description: Site.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringSiteResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
patch:
|
|
tags: [Engineering Sites]
|
|
operationId: updateEngineeringSite
|
|
summary: Update an engineering site
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.sites.manage]
|
|
x-audit-action: engineering.site.updated
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/merge-patch+json: {schema: {$ref: '#/components/schemas/UpdateEngineeringSiteRequest'}}}
|
|
responses:
|
|
'200':
|
|
description: Site updated.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringSiteResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'428': {$ref: '#/components/responses/PreconditionRequired'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/documents:
|
|
get:
|
|
tags: [Documents]
|
|
operationId: listDocuments
|
|
summary: List document metadata
|
|
description: Quarantined and infected versions are excluded unless the caller has documents.security_review.
|
|
x-required-permissions: [documents.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: classification
|
|
in: query
|
|
schema: {$ref: '#/components/schemas/DocumentClassification'}
|
|
- name: categoryId
|
|
in: query
|
|
schema: {$ref: '#/components/schemas/Uuid'}
|
|
- name: search
|
|
in: query
|
|
schema: {type: string, minLength: 1, maxLength: 200}
|
|
responses:
|
|
'200':
|
|
description: Document metadata.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DocumentCollectionResponse'}}}
|
|
'403': {$ref: '#/components/responses/Forbidden'}
|
|
|
|
/documents/upload-url:
|
|
post:
|
|
tags: [Documents]
|
|
operationId: createDocumentUploadUrl
|
|
summary: Initialize a single-part document upload
|
|
description: Creates quarantined document and version metadata, then returns a short-lived signed PUT URL.
|
|
x-required-permissions: [documents.upload]
|
|
x-audit-action: document.upload_initialized
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/InitializeDocumentUploadRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Upload initialized.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/InitializeDocumentUploadResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/documents/{documentId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DocumentId'
|
|
get:
|
|
tags: [Documents]
|
|
operationId: getDocument
|
|
summary: Retrieve document metadata
|
|
x-required-permissions: [documents.read]
|
|
responses:
|
|
'200':
|
|
description: Document metadata. No storage key or unsigned object URL is exposed.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DocumentResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
patch:
|
|
tags: [Documents]
|
|
operationId: updateDocumentMetadata
|
|
summary: Update mutable document metadata
|
|
description: Classification cannot be weakened below the linked domain record's required classification.
|
|
x-required-permissions: [documents.manage]
|
|
x-audit-action: document.metadata_updated
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/merge-patch+json: {schema: {$ref: '#/components/schemas/UpdateDocumentRequest'}}}
|
|
responses:
|
|
'200':
|
|
description: Document updated.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DocumentResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'428': {$ref: '#/components/responses/PreconditionRequired'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/documents/{documentId}/complete-upload:
|
|
post:
|
|
tags: [Documents]
|
|
operationId: completeDocumentUpload
|
|
summary: Verify a single-part upload and enqueue malware inspection
|
|
description: Completion changes uploadStatus to completed and scanStatus to pending; it never makes the file downloadable.
|
|
x-required-permissions: [documents.upload]
|
|
x-audit-action: document.upload_completed
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DocumentId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/CompleteDocumentUploadRequest'}}}
|
|
responses:
|
|
'202':
|
|
description: Object verified and security scan queued.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DocumentVersionResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/documents/{documentId}/versions:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DocumentId'
|
|
get:
|
|
tags: [Documents]
|
|
operationId: listDocumentVersions
|
|
summary: List immutable document versions
|
|
x-required-permissions: [documents.read]
|
|
responses:
|
|
'200':
|
|
description: Version metadata.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DocumentVersionCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Documents]
|
|
operationId: initializeNewDocumentVersion
|
|
summary: Initialize a new single-part version upload
|
|
description: The current version pointer changes only after upload verification and a clean scan.
|
|
x-required-permissions: [documents.upload]
|
|
x-audit-action: document.version_upload_initialized
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/InitializeDocumentVersionRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Version upload initialized.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/InitializeDocumentUploadResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/documents/{documentId}/download-url:
|
|
post:
|
|
tags: [Documents]
|
|
operationId: createDocumentDownloadUrl
|
|
summary: Create a short-lived download URL for a clean version
|
|
description: Infected, pending, failed, or quarantined versions are never downloadable.
|
|
x-required-permissions: [documents.download]
|
|
x-audit-action: document.download_authorized
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DocumentId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/CreateDocumentDownloadRequest'}}}
|
|
responses:
|
|
'200':
|
|
description: Short-lived download authorization.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DocumentDownloadResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
|
|
/documents/multipart-uploads:
|
|
post:
|
|
tags: [Documents]
|
|
operationId: initializeMultipartDocumentUpload
|
|
summary: Initialize a multipart document upload
|
|
x-required-permissions: [documents.upload]
|
|
x-audit-action: document.multipart_initialized
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/InitializeDocumentUploadRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Multipart upload initialized.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/InitializeMultipartUploadResponse'}}}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/documents/{documentId}/multipart-uploads/{uploadId}/parts:
|
|
post:
|
|
tags: [Documents]
|
|
operationId: createMultipartPartUploadUrls
|
|
summary: Create signed URLs for selected multipart parts
|
|
x-required-permissions: [documents.upload]
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DocumentId'
|
|
- $ref: '#/components/parameters/UploadId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/MultipartPartUrlsRequest'}}}
|
|
responses:
|
|
'200':
|
|
description: Signed part URLs.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/MultipartPartUrlsResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/documents/{documentId}/multipart-uploads/{uploadId}/complete:
|
|
post:
|
|
tags: [Documents]
|
|
operationId: completeMultipartDocumentUpload
|
|
summary: Assemble multipart upload and enqueue malware inspection
|
|
x-required-permissions: [documents.upload]
|
|
x-audit-action: document.multipart_completed
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DocumentId'
|
|
- $ref: '#/components/parameters/UploadId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/CompleteMultipartUploadRequest'}}}
|
|
responses:
|
|
'202':
|
|
description: Multipart object assembled and security scan queued.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DocumentVersionResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/documents/{documentId}/multipart-uploads/{uploadId}:
|
|
delete:
|
|
tags: [Documents]
|
|
operationId: abortMultipartDocumentUpload
|
|
summary: Abort an unfinished multipart upload
|
|
x-required-permissions: [documents.upload]
|
|
x-audit-action: document.multipart_aborted
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DocumentId'
|
|
- $ref: '#/components/parameters/UploadId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
responses:
|
|
'204': {description: Upload aborted; staged object parts are scheduled for cleanup.}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
|
|
/engineering/projects/{projectId}/documents:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
get:
|
|
tags: [Engineering Project Documents]
|
|
operationId: listEngineeringProjectDocuments
|
|
summary: List active project-document links
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.documents.read]
|
|
responses:
|
|
'200':
|
|
description: Project documents filtered by document authorization and scan state.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringProjectDocumentCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Engineering Project Documents]
|
|
operationId: linkEngineeringProjectDocument
|
|
summary: Link a clean shared document to a project
|
|
description: Pending, failed, or infected versions cannot be linked as the active project document.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.documents.manage]
|
|
x-audit-action: engineering.project_document.linked
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/LinkEngineeringProjectDocumentRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Document linked.
|
|
headers: {Location: {$ref: '#/components/headers/Location'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringProjectDocumentResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/project-documents/{documentLinkId}:
|
|
delete:
|
|
tags: [Engineering Project Documents]
|
|
operationId: unlinkEngineeringProjectDocument
|
|
summary: Temporally unlink a document from a project
|
|
description: Sets unlinkedAt; it does not delete the shared document or its versions.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.documents.manage]
|
|
x-audit-action: engineering.project_document.unlinked
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DocumentLinkId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
responses:
|
|
'204': {description: Link ended.}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
|
|
/engineering/projects/{projectId}/designs:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
get:
|
|
tags: [Engineering Designs]
|
|
operationId: listEngineeringProjectDesigns
|
|
summary: List designs for a project
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: status
|
|
in: query
|
|
schema: {$ref: '#/components/schemas/EngineeringDesignStatus'}
|
|
- name: discipline
|
|
in: query
|
|
schema: {type: string, minLength: 1, maxLength: 100}
|
|
responses:
|
|
'200':
|
|
description: Project designs.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringDesignCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Engineering Designs]
|
|
operationId: createEngineeringDesign
|
|
summary: Create a draft design
|
|
description: Atomically creates version 1 and the required owner/preparer assignments.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.manage]
|
|
x-audit-action: engineering.design.created
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/CreateEngineeringDesignRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Draft design and initial version created.
|
|
headers: {Location: {$ref: '#/components/headers/Location'}, ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringDesignResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/designs/{designId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DesignId'
|
|
get:
|
|
tags: [Engineering Designs]
|
|
operationId: getEngineeringDesign
|
|
summary: Retrieve a design
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.read]
|
|
responses:
|
|
'200':
|
|
description: Design.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringDesignResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
patch:
|
|
tags: [Engineering Designs]
|
|
operationId: updateEngineeringDesign
|
|
summary: Update editable design metadata
|
|
description: Only draft or changes_requested designs are editable; status changes use commands.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.manage]
|
|
x-audit-action: engineering.design.updated
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/merge-patch+json: {schema: {$ref: '#/components/schemas/UpdateEngineeringDesignRequest'}}}
|
|
responses:
|
|
'200':
|
|
description: Design updated.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringDesignResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'428': {$ref: '#/components/responses/PreconditionRequired'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/designs/{designId}/submit-review:
|
|
post:
|
|
tags: [Engineering Designs]
|
|
operationId: submitEngineeringDesignForReview
|
|
summary: Submit the current version for review
|
|
description: Requires a clean primary drawing and at least one active reviewer assignment.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.submit]
|
|
x-audit-action: engineering.design.submitted_for_review
|
|
parameters: &designCommandParameters
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DesignId'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/OptionalDesignReasonCommand'}}}
|
|
responses: &designCommandResponses
|
|
'200':
|
|
description: Design transitioned.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringDesignResponse'}}}
|
|
'403': {$ref: '#/components/responses/Forbidden'}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'428': {$ref: '#/components/responses/PreconditionRequired'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/designs/{designId}/request-changes:
|
|
post:
|
|
tags: [Engineering Designs]
|
|
operationId: requestEngineeringDesignChanges
|
|
summary: Return a design to changes requested
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.review]
|
|
x-audit-action: engineering.design.changes_requested
|
|
parameters: *designCommandParameters
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DesignDecisionCommand'}}}
|
|
responses: *designCommandResponses
|
|
|
|
/engineering/designs/{designId}/approve:
|
|
post:
|
|
tags: [Engineering Designs]
|
|
operationId: approveEngineeringDesign
|
|
summary: Professionally approve the current design version
|
|
description: Revalidates current credential, discipline, scope-of-practice, and approval policy.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.approve]
|
|
x-audit-action: engineering.design.approved
|
|
parameters: *designCommandParameters
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/ApproveDesignCommand'}}}
|
|
responses: *designCommandResponses
|
|
|
|
/engineering/designs/{designId}/reject:
|
|
post:
|
|
tags: [Engineering Designs]
|
|
operationId: rejectEngineeringDesign
|
|
summary: Reject the current design version
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.review]
|
|
x-audit-action: engineering.design.rejected
|
|
parameters: *designCommandParameters
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DesignDecisionCommand'}}}
|
|
responses: *designCommandResponses
|
|
|
|
/engineering/designs/{designId}/revise:
|
|
post:
|
|
tags: [Engineering Designs]
|
|
operationId: reviseRejectedEngineeringDesign
|
|
summary: Reopen a rejected design as draft with a new version
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.manage]
|
|
x-audit-action: engineering.design.revised
|
|
parameters: *designCommandParameters
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DesignReasonCommand'}}}
|
|
responses: *designCommandResponses
|
|
|
|
/engineering/designs/{designId}/cancel:
|
|
post:
|
|
tags: [Engineering Designs]
|
|
operationId: cancelEngineeringDesign
|
|
summary: Cancel a draft design
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.manage]
|
|
x-audit-action: engineering.design.cancelled
|
|
parameters: *designCommandParameters
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DesignReasonCommand'}}}
|
|
responses: *designCommandResponses
|
|
|
|
/engineering/designs/{designId}/withdraw:
|
|
post:
|
|
tags: [Engineering Designs]
|
|
operationId: withdrawEngineeringDesign
|
|
summary: Withdraw a design from review
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.manage]
|
|
x-audit-action: engineering.design.withdrawn
|
|
parameters: *designCommandParameters
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/DesignReasonCommand'}}}
|
|
responses: *designCommandResponses
|
|
|
|
/engineering/designs/{designId}/supersede:
|
|
post:
|
|
tags: [Engineering Designs]
|
|
operationId: supersedeEngineeringDesign
|
|
summary: Supersede an approved design
|
|
description: Requires the replacement to be a different approved design in the same project and discipline.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.approve]
|
|
x-audit-action: engineering.design.superseded
|
|
parameters: *designCommandParameters
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/SupersedeDesignCommand'}}}
|
|
responses: *designCommandResponses
|
|
|
|
/engineering/designs/{designId}/assignments:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DesignId'
|
|
get:
|
|
tags: [Engineering Design Assignments]
|
|
operationId: listEngineeringDesignAssignments
|
|
summary: List current and historical design assignments
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.read]
|
|
parameters:
|
|
- name: activeOnly
|
|
in: query
|
|
schema: {type: boolean, default: true}
|
|
responses:
|
|
'200':
|
|
description: Assignments.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringDesignAssignmentCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Engineering Design Assignments]
|
|
operationId: assignEngineeringDesignParticipant
|
|
summary: Assign a member to a design role
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.assign]
|
|
x-audit-action: engineering.design.assignment_created
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/AssignEngineeringDesignRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Assignment created.
|
|
headers: {Location: {$ref: '#/components/headers/Location'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringDesignAssignmentResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/designs/{designId}/unassign:
|
|
post:
|
|
tags: [Engineering Design Assignments]
|
|
operationId: unassignEngineeringDesignParticipant
|
|
summary: End an active design assignment
|
|
description: Sets unassignedAt. The final active owner or required reviewer cannot be removed while workflow depends on that role.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.assign]
|
|
x-audit-action: engineering.design.assignment_ended
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DesignId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/UnassignEngineeringDesignRequest'}}}
|
|
responses:
|
|
'204': {description: Assignment ended.}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
|
|
/engineering/designs/{designId}/versions:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DesignId'
|
|
get:
|
|
tags: [Engineering Design Versions]
|
|
operationId: listEngineeringDesignVersions
|
|
summary: List immutable logical design versions
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.read]
|
|
responses:
|
|
'200':
|
|
description: Design versions.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringDesignVersionCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Engineering Design Versions]
|
|
operationId: createEngineeringDesignVersion
|
|
summary: Create the next logical design version
|
|
description: Allowed only in draft or changes_requested. Version numbers are allocated transactionally.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.manage]
|
|
x-audit-action: engineering.design.version_created
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/CreateEngineeringDesignVersionRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Design version created.
|
|
headers: {Location: {$ref: '#/components/headers/Location'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringDesignVersionResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
|
|
/engineering/design-versions/{designVersionId}/documents:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DesignVersionId'
|
|
get:
|
|
tags: [Engineering Design Versions]
|
|
operationId: listEngineeringDesignVersionDocuments
|
|
summary: List documents linked to a design version
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.read]
|
|
responses:
|
|
'200':
|
|
description: Version documents.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringDesignVersionDocumentCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Engineering Design Versions]
|
|
operationId: linkEngineeringDesignVersionDocument
|
|
summary: Link a clean document to an editable design version
|
|
description: Only scan-clean documents may be linked; a version may have only one active primary_drawing.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.manage]
|
|
x-audit-action: engineering.design.version_document_linked
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/LinkEngineeringDesignVersionDocumentRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Document linked.
|
|
headers: {Location: {$ref: '#/components/headers/Location'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringDesignVersionDocumentResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/design-version-documents/{documentLinkId}:
|
|
delete:
|
|
tags: [Engineering Design Versions]
|
|
operationId: unlinkEngineeringDesignVersionDocument
|
|
summary: Temporally unlink a document from an editable design version
|
|
description: Submitted, approved, rejected, or superseded version evidence cannot be unlinked.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.manage]
|
|
x-audit-action: engineering.design.version_document_unlinked
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DocumentLinkId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
responses:
|
|
'204': {description: Link ended.}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
|
|
/engineering/designs/{designId}/reviews:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DesignId'
|
|
get:
|
|
tags: [Engineering Design Reviews]
|
|
operationId: listEngineeringDesignReviews
|
|
summary: List review recommendations
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.read]
|
|
responses:
|
|
'200':
|
|
description: Reviews.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringDesignReviewCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Engineering Design Reviews]
|
|
operationId: recordEngineeringDesignReview
|
|
summary: Record a reviewer recommendation for the submitted version
|
|
description: A recommendation is immutable and never directly changes design status.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.designs.review]
|
|
x-audit-action: engineering.design.review_recorded
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/CreateEngineeringDesignReviewRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Review recommendation recorded.
|
|
headers: {Location: {$ref: '#/components/headers/Location'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringDesignReviewResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/projects/{projectId}/inspections:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
get:
|
|
tags: [Engineering Inspections]
|
|
operationId: listEngineeringProjectInspections
|
|
summary: List inspections for a project
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: siteId
|
|
in: query
|
|
schema: {$ref: '#/components/schemas/Uuid'}
|
|
- name: status
|
|
in: query
|
|
schema: {$ref: '#/components/schemas/EngineeringInspectionStatus'}
|
|
responses:
|
|
'200':
|
|
description: Project inspections.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Engineering Inspections]
|
|
operationId: createEngineeringInspection
|
|
summary: Create a draft inspection
|
|
description: Site and inspector must belong to the same project and active organization context.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.manage]
|
|
x-audit-action: engineering.inspection.created
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/CreateEngineeringInspectionRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Draft inspection created.
|
|
headers: {Location: {$ref: '#/components/headers/Location'}, ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/inspections/{inspectionId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/InspectionId'
|
|
get:
|
|
tags: [Engineering Inspections]
|
|
operationId: getEngineeringInspection
|
|
summary: Retrieve an inspection
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.read]
|
|
responses:
|
|
'200':
|
|
description: Inspection.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
patch:
|
|
tags: [Engineering Inspections]
|
|
operationId: updateEngineeringInspection
|
|
summary: Update editable inspection metadata
|
|
description: Draft and scheduled inspections are editable; lifecycle fields use commands.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.manage]
|
|
x-audit-action: engineering.inspection.updated
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/merge-patch+json: {schema: {$ref: '#/components/schemas/UpdateEngineeringInspectionRequest'}}}
|
|
responses:
|
|
'200':
|
|
description: Inspection updated.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'428': {$ref: '#/components/responses/PreconditionRequired'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/inspections/{inspectionId}/schedule:
|
|
post:
|
|
tags: [Engineering Inspections]
|
|
operationId: scheduleEngineeringInspection
|
|
summary: Schedule a draft inspection
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.manage]
|
|
x-audit-action: engineering.inspection.scheduled
|
|
parameters: &inspectionCommandParameters
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/InspectionId'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/ScheduleInspectionCommand'}}}
|
|
responses: &inspectionCommandResponses
|
|
'200':
|
|
description: Inspection transitioned.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionResponse'}}}
|
|
'403': {$ref: '#/components/responses/Forbidden'}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'428': {$ref: '#/components/responses/PreconditionRequired'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/inspections/{inspectionId}/start:
|
|
post:
|
|
tags: [Engineering Inspections]
|
|
operationId: startEngineeringInspection
|
|
summary: Start a scheduled inspection
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.perform]
|
|
x-audit-action: engineering.inspection.started
|
|
parameters: *inspectionCommandParameters
|
|
requestBody:
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/StartInspectionCommand'}}}
|
|
responses: *inspectionCommandResponses
|
|
|
|
/engineering/inspections/{inspectionId}/complete:
|
|
post:
|
|
tags: [Engineering Inspections]
|
|
operationId: completeEngineeringInspection
|
|
summary: Complete an in-progress inspection
|
|
description: Outcome is mandatory. Passed outcomes are rejected while major or critical findings remain open.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.perform]
|
|
x-audit-action: engineering.inspection.completed
|
|
parameters: *inspectionCommandParameters
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/CompleteInspectionCommand'}}}
|
|
responses: *inspectionCommandResponses
|
|
|
|
/engineering/inspections/{inspectionId}/cancel:
|
|
post:
|
|
tags: [Engineering Inspections]
|
|
operationId: cancelEngineeringInspection
|
|
summary: Cancel a draft or scheduled inspection
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.manage]
|
|
x-audit-action: engineering.inspection.cancelled
|
|
parameters: *inspectionCommandParameters
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/InspectionReasonCommand'}}}
|
|
responses: *inspectionCommandResponses
|
|
|
|
/engineering/inspections/{inspectionId}/documents:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/InspectionId'
|
|
get:
|
|
tags: [Engineering Inspection Documents]
|
|
operationId: listEngineeringInspectionDocuments
|
|
summary: List active inspection-document links
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.read]
|
|
responses:
|
|
'200':
|
|
description: Inspection documents.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionDocumentCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Engineering Inspection Documents]
|
|
operationId: linkEngineeringInspectionDocument
|
|
summary: Link a scan-clean document to an inspection
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.manage]
|
|
x-audit-action: engineering.inspection.document_linked
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/LinkEngineeringInspectionDocumentRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Document linked.
|
|
headers: {Location: {$ref: '#/components/headers/Location'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionDocumentResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/inspection-documents/{documentLinkId}:
|
|
delete:
|
|
tags: [Engineering Inspection Documents]
|
|
operationId: unlinkEngineeringInspectionDocument
|
|
summary: Temporally unlink an inspection document
|
|
description: Completed inspection evidence cannot be unlinked through ordinary workflow.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.manage]
|
|
x-audit-action: engineering.inspection.document_unlinked
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DocumentLinkId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
responses:
|
|
'204': {description: Link ended.}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
|
|
/engineering/inspections/{inspectionId}/findings:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/InspectionId'
|
|
get:
|
|
tags: [Engineering Inspection Findings]
|
|
operationId: listEngineeringInspectionFindings
|
|
summary: List findings for an inspection
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.read]
|
|
responses:
|
|
'200':
|
|
description: Inspection findings.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionFindingCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Engineering Inspection Findings]
|
|
operationId: createEngineeringInspectionFinding
|
|
summary: Record a finding during an in-progress inspection
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.perform]
|
|
x-audit-action: engineering.inspection.finding_created
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/CreateEngineeringInspectionFindingRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Finding recorded.
|
|
headers: {Location: {$ref: '#/components/headers/Location'}, ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionFindingResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/inspection-findings/{findingId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/FindingId'
|
|
get:
|
|
tags: [Engineering Inspection Findings]
|
|
operationId: getEngineeringInspectionFinding
|
|
summary: Retrieve an inspection finding
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.read]
|
|
responses:
|
|
'200':
|
|
description: Finding.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionFindingResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
patch:
|
|
tags: [Engineering Inspection Findings]
|
|
operationId: updateEngineeringInspectionFinding
|
|
summary: Update finding description, severity, owner, or target date
|
|
description: Resolved and accepted-risk findings are immutable except through explicit reopen policy added later.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.manage_findings]
|
|
x-audit-action: engineering.inspection.finding_updated
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/merge-patch+json: {schema: {$ref: '#/components/schemas/UpdateEngineeringInspectionFindingRequest'}}}
|
|
responses:
|
|
'200':
|
|
description: Finding updated.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionFindingResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'428': {$ref: '#/components/responses/PreconditionRequired'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/inspection-findings/{findingId}/start-remediation:
|
|
post:
|
|
tags: [Engineering Inspection Findings]
|
|
operationId: startEngineeringFindingRemediation
|
|
summary: Start corrective work for an open finding
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.manage_findings]
|
|
x-audit-action: engineering.inspection.finding_remediation_started
|
|
parameters: &findingCommandParameters
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/FindingId'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
responses: &findingCommandResponses
|
|
'200':
|
|
description: Finding transitioned.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionFindingResponse'}}}
|
|
'403': {$ref: '#/components/responses/Forbidden'}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'428': {$ref: '#/components/responses/PreconditionRequired'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/inspection-findings/{findingId}/resolve:
|
|
post:
|
|
tags: [Engineering Inspection Findings]
|
|
operationId: resolveEngineeringInspectionFinding
|
|
summary: Independently verify and resolve a remediated finding
|
|
description: Verifier must differ from remediation owner unless an explicit privileged override is audited.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.verify_findings]
|
|
x-audit-action: engineering.inspection.finding_resolved
|
|
parameters: *findingCommandParameters
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/ResolveEngineeringFindingCommand'}}}
|
|
responses: *findingCommandResponses
|
|
|
|
/engineering/inspection-findings/{findingId}/accept-risk:
|
|
post:
|
|
tags: [Engineering Inspection Findings]
|
|
operationId: acceptEngineeringInspectionFindingRisk
|
|
summary: Accept the documented risk of an unresolved finding
|
|
description: Major and critical acceptance requires privileged authority and a review date.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.accept_risk]
|
|
x-audit-action: engineering.inspection.finding_risk_accepted
|
|
parameters: *findingCommandParameters
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/AcceptEngineeringFindingRiskCommand'}}}
|
|
responses: *findingCommandResponses
|
|
|
|
/engineering/inspections/{inspectionId}/followups:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/InspectionId'
|
|
get:
|
|
tags: [Engineering Inspection Follow-ups]
|
|
operationId: listEngineeringInspectionFollowups
|
|
summary: List follow-up actions
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.read]
|
|
responses:
|
|
'200':
|
|
description: Follow-ups.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionFollowupCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Engineering Inspection Follow-ups]
|
|
operationId: createEngineeringInspectionFollowup
|
|
summary: Create a corrective-task or follow-up-inspection relationship
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.manage_findings]
|
|
x-audit-action: engineering.inspection.followup_created
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/CreateEngineeringInspectionFollowupRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Follow-up created.
|
|
headers: {Location: {$ref: '#/components/headers/Location'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionFollowupResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/inspection-followups/{followupId}/{command}:
|
|
post:
|
|
tags: [Engineering Inspection Follow-ups]
|
|
operationId: commandEngineeringInspectionFollowup
|
|
summary: Start, complete, or cancel a follow-up
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.inspections.manage_findings]
|
|
x-audit-action: engineering.inspection.followup_commanded
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/FollowupId'
|
|
- name: command
|
|
in: path
|
|
required: true
|
|
schema: {type: string, enum: [start, complete, cancel]}
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/OptionalInspectionReasonCommand'}}}
|
|
responses:
|
|
'200':
|
|
description: Follow-up transitioned.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringInspectionFollowupResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
|
|
/engineering/projects/{projectId}/specifications:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/ProjectId'
|
|
get:
|
|
tags: [Engineering Specifications]
|
|
operationId: listEngineeringProjectSpecifications
|
|
summary: List specifications for a project
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.specifications.read]
|
|
parameters:
|
|
- $ref: '#/components/parameters/Limit'
|
|
- $ref: '#/components/parameters/Cursor'
|
|
- name: status
|
|
in: query
|
|
schema: {$ref: '#/components/schemas/EngineeringSpecificationStatus'}
|
|
- name: search
|
|
in: query
|
|
schema: {type: string, minLength: 1, maxLength: 200}
|
|
responses:
|
|
'200':
|
|
description: Project specifications.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringSpecificationCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Engineering Specifications]
|
|
operationId: createEngineeringSpecification
|
|
summary: Create a draft specification
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.specifications.manage]
|
|
x-audit-action: engineering.specification.created
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/CreateEngineeringSpecificationRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Draft specification created.
|
|
headers: {Location: {$ref: '#/components/headers/Location'}, ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringSpecificationResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/specifications/{specificationId}:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/SpecificationId'
|
|
get:
|
|
tags: [Engineering Specifications]
|
|
operationId: getEngineeringSpecification
|
|
summary: Retrieve a specification
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.specifications.read]
|
|
responses:
|
|
'200':
|
|
description: Specification.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringSpecificationResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
patch:
|
|
tags: [Engineering Specifications]
|
|
operationId: updateEngineeringSpecification
|
|
summary: Update draft specification metadata
|
|
description: Active, superseded and archived specifications reject PATCH; lifecycle changes use commands.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.specifications.manage]
|
|
x-audit-action: engineering.specification.updated
|
|
parameters:
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/merge-patch+json: {schema: {$ref: '#/components/schemas/UpdateEngineeringSpecificationRequest'}}}
|
|
responses:
|
|
'200':
|
|
description: Specification updated.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringSpecificationResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'428': {$ref: '#/components/responses/PreconditionRequired'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/specifications/{specificationId}/activate:
|
|
post:
|
|
tags: [Engineering Specifications]
|
|
operationId: activateEngineeringSpecification
|
|
summary: Activate a draft specification
|
|
description: Requires exactly one active, scan-clean primary document.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.specifications.activate]
|
|
x-audit-action: engineering.specification.activated
|
|
parameters: &specificationCommandParameters
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/SpecificationId'
|
|
- $ref: '#/components/parameters/IfMatch'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/OptionalSpecificationReasonCommand'}}}
|
|
responses: &specificationCommandResponses
|
|
'200':
|
|
description: Specification transitioned.
|
|
headers: {ETag: {$ref: '#/components/headers/ETag'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringSpecificationResponse'}}}
|
|
'403': {$ref: '#/components/responses/Forbidden'}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'428': {$ref: '#/components/responses/PreconditionRequired'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/specifications/{specificationId}/supersede:
|
|
post:
|
|
tags: [Engineering Specifications]
|
|
operationId: supersedeEngineeringSpecification
|
|
summary: Supersede an active specification
|
|
description: Replacement must be a different active specification in the same project.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.specifications.activate]
|
|
x-audit-action: engineering.specification.superseded
|
|
parameters: *specificationCommandParameters
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/SupersedeSpecificationCommand'}}}
|
|
responses: *specificationCommandResponses
|
|
|
|
/engineering/specifications/{specificationId}/archive:
|
|
post:
|
|
tags: [Engineering Specifications]
|
|
operationId: archiveEngineeringSpecification
|
|
summary: Archive a draft or active specification
|
|
description: Stores archivedFromStatus; active specifications referenced by open work may be blocked.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.specifications.manage]
|
|
x-audit-action: engineering.specification.archived
|
|
parameters: *specificationCommandParameters
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/SpecificationReasonCommand'}}}
|
|
responses: *specificationCommandResponses
|
|
|
|
/engineering/specifications/{specificationId}/restore:
|
|
post:
|
|
tags: [Engineering Specifications]
|
|
operationId: restoreEngineeringSpecification
|
|
summary: Restore an archived specification to its prior status
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.specifications.manage]
|
|
x-audit-action: engineering.specification.restored
|
|
parameters: *specificationCommandParameters
|
|
requestBody:
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/OptionalSpecificationReasonCommand'}}}
|
|
responses: *specificationCommandResponses
|
|
|
|
/engineering/specifications/{specificationId}/documents:
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/SpecificationId'
|
|
get:
|
|
tags: [Engineering Specification Documents]
|
|
operationId: listEngineeringSpecificationDocuments
|
|
summary: List current and historical specification-document links
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.specifications.read]
|
|
parameters:
|
|
- name: activeOnly
|
|
in: query
|
|
schema: {type: boolean, default: true}
|
|
responses:
|
|
'200':
|
|
description: Specification documents.
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringSpecificationDocumentCollectionResponse'}}}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
post:
|
|
tags: [Engineering Specification Documents]
|
|
operationId: linkEngineeringSpecificationDocument
|
|
summary: Link a scan-clean document to a draft specification
|
|
description: A draft may have only one active primary link; active or terminal evidence is immutable.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.specifications.manage]
|
|
x-audit-action: engineering.specification.document_linked
|
|
parameters:
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
requestBody:
|
|
required: true
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/LinkEngineeringSpecificationDocumentRequest'}}}
|
|
responses:
|
|
'201':
|
|
description: Document linked.
|
|
headers: {Location: {$ref: '#/components/headers/Location'}}
|
|
content: {application/json: {schema: {$ref: '#/components/schemas/EngineeringSpecificationDocumentResponse'}}}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
'422': {$ref: '#/components/responses/ValidationError'}
|
|
|
|
/engineering/specification-documents/{documentLinkId}:
|
|
delete:
|
|
tags: [Engineering Specification Documents]
|
|
operationId: unlinkEngineeringSpecificationDocument
|
|
summary: Temporally unlink a document from a draft specification
|
|
description: Sets unlinkedAt; it never deletes the shared document or active/terminal evidence.
|
|
x-required-profession: engineering
|
|
x-required-permissions: [engineering.specifications.manage]
|
|
x-audit-action: engineering.specification.document_unlinked
|
|
parameters:
|
|
- $ref: '#/components/parameters/OrganizationContext'
|
|
- $ref: '#/components/parameters/RequestId'
|
|
- $ref: '#/components/parameters/DocumentLinkId'
|
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
responses:
|
|
'204': {description: Link ended.}
|
|
'404': {$ref: '#/components/responses/NotFound'}
|
|
'409': {$ref: '#/components/responses/Conflict'}
|
|
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
|
|
parameters:
|
|
OrganizationContext:
|
|
name: X-Organization-Id
|
|
in: header
|
|
required: true
|
|
description: Active organization context for the tenant-scoped request.
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
RequestId:
|
|
name: X-Request-Id
|
|
in: header
|
|
required: false
|
|
description: Client-generated request identifier. The server generates one when omitted.
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
IdempotencyKey:
|
|
name: Idempotency-Key
|
|
in: header
|
|
required: true
|
|
description: |
|
|
Unique key for replay-safe execution. Reuse with a different normalized request
|
|
returns `IDEMPOTENCY_KEY_CONFLICT`.
|
|
schema:
|
|
type: string
|
|
minLength: 16
|
|
maxLength: 128
|
|
IfMatch:
|
|
name: If-Match
|
|
in: header
|
|
required: true
|
|
description: ETag returned by the latest representation of the resource.
|
|
schema:
|
|
type: string
|
|
minLength: 3
|
|
maxLength: 128
|
|
Limit:
|
|
name: limit
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 100
|
|
default: 25
|
|
Cursor:
|
|
name: cursor
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 2048
|
|
OrganizationId:
|
|
name: organizationId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
SessionId:
|
|
name: sessionId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
InvitationId:
|
|
name: invitationId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
MembershipId:
|
|
name: membershipId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
RoleId:
|
|
name: roleId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
ClientId:
|
|
name: clientId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
ContactId:
|
|
name: contactId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
ProjectId:
|
|
name: projectId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
ProjectMemberId:
|
|
name: memberId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
TaskId:
|
|
name: taskId
|
|
in: path
|
|
required: true
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
SiteId:
|
|
name: siteId
|
|
in: path
|
|
required: true
|
|
schema: {$ref: '#/components/schemas/Uuid'}
|
|
DocumentId:
|
|
name: documentId
|
|
in: path
|
|
required: true
|
|
schema: {$ref: '#/components/schemas/Uuid'}
|
|
UploadId:
|
|
name: uploadId
|
|
in: path
|
|
required: true
|
|
schema: {$ref: '#/components/schemas/Uuid'}
|
|
DocumentLinkId:
|
|
name: documentLinkId
|
|
in: path
|
|
required: true
|
|
schema: {$ref: '#/components/schemas/Uuid'}
|
|
DesignId:
|
|
name: designId
|
|
in: path
|
|
required: true
|
|
schema: {$ref: '#/components/schemas/Uuid'}
|
|
DesignVersionId:
|
|
name: designVersionId
|
|
in: path
|
|
required: true
|
|
schema: {$ref: '#/components/schemas/Uuid'}
|
|
InspectionId:
|
|
name: inspectionId
|
|
in: path
|
|
required: true
|
|
schema: {$ref: '#/components/schemas/Uuid'}
|
|
FindingId:
|
|
name: findingId
|
|
in: path
|
|
required: true
|
|
schema: {$ref: '#/components/schemas/Uuid'}
|
|
FollowupId:
|
|
name: followupId
|
|
in: path
|
|
required: true
|
|
schema: {$ref: '#/components/schemas/Uuid'}
|
|
SpecificationId:
|
|
name: specificationId
|
|
in: path
|
|
required: true
|
|
schema: {$ref: '#/components/schemas/Uuid'}
|
|
|
|
headers:
|
|
RequestId:
|
|
description: Request identifier used for logs, audit, and diagnostics.
|
|
schema:
|
|
$ref: '#/components/schemas/Uuid'
|
|
ETag:
|
|
description: Strong validator for optimistic concurrency.
|
|
schema:
|
|
type: string
|
|
examples: ['"6"']
|
|
Location:
|
|
description: Canonical URI of the created resource.
|
|
schema:
|
|
type: string
|
|
format: uri-reference
|
|
RetryAfter:
|
|
description: Seconds or HTTP date after which the client may retry.
|
|
schema:
|
|
oneOf:
|
|
- type: integer
|
|
minimum: 0
|
|
- type: string
|
|
|
|
responses:
|
|
BadRequest:
|
|
description: Request is malformed or required organization context is missing.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: '#/components/schemas/Problem'
|
|
examples:
|
|
organizationContextRequired:
|
|
value:
|
|
type: https://api.example.com/problems/organization-context-required
|
|
title: Organization context required
|
|
status: 400
|
|
detail: X-Organization-Id is required for this operation.
|
|
code: ORGANIZATION_CONTEXT_REQUIRED
|
|
requestId: 0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2cff
|
|
Unauthorized:
|
|
description: Authentication is missing, invalid, expired, or revoked.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: '#/components/schemas/Problem'
|
|
examples:
|
|
invalidToken:
|
|
value:
|
|
type: https://api.example.com/problems/auth-token-invalid
|
|
title: Authentication failed
|
|
status: 401
|
|
detail: The access token is invalid.
|
|
code: AUTH_TOKEN_INVALID
|
|
requestId: 0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2cff
|
|
Forbidden:
|
|
description: The authenticated actor is not permitted to perform the operation.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: '#/components/schemas/Problem'
|
|
NotFound:
|
|
description: Resource not found, including cross-tenant resource access.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: '#/components/schemas/Problem'
|
|
examples:
|
|
notFound:
|
|
value:
|
|
type: https://api.example.com/problems/resource-not-found
|
|
title: Resource not found
|
|
status: 404
|
|
detail: The requested resource was not found.
|
|
code: RESOURCE_NOT_FOUND
|
|
requestId: 0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2cff
|
|
Conflict:
|
|
description: Conflict with an existing resource, state, idempotency record, or version.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: '#/components/schemas/Problem'
|
|
ValidationError:
|
|
description: Request is structurally valid but fails field or business validation.
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: '#/components/schemas/Problem'
|
|
examples:
|
|
invalidEmail:
|
|
value:
|
|
type: https://api.example.com/problems/validation-error
|
|
title: Request validation failed
|
|
status: 422
|
|
detail: One or more fields are invalid.
|
|
code: VALIDATION_ERROR
|
|
requestId: 0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2cff
|
|
errors:
|
|
- field: email
|
|
code: INVALID_FORMAT
|
|
message: Must be a valid email address.
|
|
PreconditionRequired:
|
|
description: "`If-Match` is required for this mutation."
|
|
headers:
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: '#/components/schemas/Problem'
|
|
RateLimited:
|
|
description: Request rate limit exceeded.
|
|
headers:
|
|
Retry-After:
|
|
$ref: '#/components/headers/RetryAfter'
|
|
X-Request-Id:
|
|
$ref: '#/components/headers/RequestId'
|
|
content:
|
|
application/problem+json:
|
|
schema:
|
|
$ref: '#/components/schemas/Problem'
|
|
|
|
schemas:
|
|
Uuid:
|
|
type: string
|
|
format: uuid
|
|
description: UUIDv7 serialized in canonical lowercase form.
|
|
examples: [0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c1d]
|
|
Timestamp:
|
|
type: string
|
|
format: date-time
|
|
examples: ['2026-08-26T12:00:00Z']
|
|
Date:
|
|
type: string
|
|
format: date
|
|
examples: ['2026-08-26']
|
|
Email:
|
|
type: string
|
|
format: email
|
|
maxLength: 320
|
|
CountryCode:
|
|
type: string
|
|
pattern: '^[A-Z]{2}$'
|
|
examples: [MA]
|
|
CurrencyCode:
|
|
type: string
|
|
pattern: '^[A-Z]{3}$'
|
|
examples: [MAD]
|
|
EngineeringSite:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, projectId, name, address, version, createdAt, updatedAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
projectId: {$ref: '#/components/schemas/Uuid'}
|
|
name: {type: string, minLength: 1, maxLength: 200}
|
|
address: {$ref: '#/components/schemas/EngineeringSiteAddress'}
|
|
latitude: {type: [number, 'null'], minimum: -90, maximum: 90}
|
|
longitude: {type: [number, 'null'], minimum: -180, maximum: 180}
|
|
version: {type: integer, minimum: 1}
|
|
createdAt: {$ref: '#/components/schemas/Timestamp'}
|
|
updatedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
EngineeringSiteAddress:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [line1, city, countryCode]
|
|
properties:
|
|
line1: {type: string, minLength: 1, maxLength: 200}
|
|
line2: {type: [string, 'null'], maxLength: 200}
|
|
city: {type: string, minLength: 1, maxLength: 120}
|
|
region: {type: [string, 'null'], maxLength: 120}
|
|
postalCode: {type: [string, 'null'], maxLength: 32}
|
|
countryCode: {$ref: '#/components/schemas/CountryCode'}
|
|
CreateEngineeringSiteRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [name, address]
|
|
properties:
|
|
name: {type: string, minLength: 1, maxLength: 200}
|
|
address: {$ref: '#/components/schemas/EngineeringSiteAddress'}
|
|
latitude: {type: [number, 'null'], minimum: -90, maximum: 90}
|
|
longitude: {type: [number, 'null'], minimum: -180, maximum: 180}
|
|
UpdateEngineeringSiteRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
minProperties: 1
|
|
properties:
|
|
name: {type: string, minLength: 1, maxLength: 200}
|
|
address: {$ref: '#/components/schemas/EngineeringSiteAddress'}
|
|
latitude: {type: [number, 'null'], minimum: -90, maximum: 90}
|
|
longitude: {type: [number, 'null'], minimum: -180, maximum: 180}
|
|
EngineeringSiteResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/EngineeringSite'}}
|
|
EngineeringSiteCollectionResponse:
|
|
type: object
|
|
required: [data, meta]
|
|
properties:
|
|
data: {type: array, items: {$ref: '#/components/schemas/EngineeringSite'}}
|
|
meta: {$ref: '#/components/schemas/CollectionMeta'}
|
|
DocumentClassification:
|
|
type: string
|
|
enum: [public, internal, confidential, restricted, regulated]
|
|
DocumentUploadStatus:
|
|
type: string
|
|
enum: [initialized, uploading, completed, failed, aborted, expired]
|
|
MalwareScanStatus:
|
|
type: string
|
|
enum: [not_started, pending, scanning, clean, infected, failed]
|
|
Document:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, name, classification, currentVersionId, version, createdByUserId, createdAt, updatedAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
name: {type: string, minLength: 1, maxLength: 255}
|
|
categoryId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
classification: {$ref: '#/components/schemas/DocumentClassification'}
|
|
retentionPolicyId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
currentVersionId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
currentVersion: {oneOf: [{$ref: '#/components/schemas/DocumentVersion'}, {type: 'null'}]}
|
|
version: {type: integer, minimum: 1}
|
|
createdByUserId: {$ref: '#/components/schemas/Uuid'}
|
|
createdAt: {$ref: '#/components/schemas/Timestamp'}
|
|
updatedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
DocumentVersion:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, documentId, versionNumber, mimeType, sizeBytes, uploadStatus, scanStatus, uploadedByUserId, createdAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
documentId: {$ref: '#/components/schemas/Uuid'}
|
|
versionNumber: {type: integer, minimum: 1}
|
|
mimeType: {type: string, minLength: 1, maxLength: 255}
|
|
sizeBytes: {type: integer, minimum: 1, maximum: 5368709120}
|
|
contentHash: {type: [string, 'null'], pattern: '^sha256:[a-f0-9]{64}$'}
|
|
hashAlgorithm: {type: string, const: sha256}
|
|
uploadStatus: {$ref: '#/components/schemas/DocumentUploadStatus'}
|
|
scanStatus: {$ref: '#/components/schemas/MalwareScanStatus'}
|
|
scanCompletedAt: {type: [string, 'null'], format: date-time}
|
|
available: {type: boolean, readOnly: true, description: True only when uploadStatus is completed and scanStatus is clean.}
|
|
uploadedByUserId: {$ref: '#/components/schemas/Uuid'}
|
|
createdAt: {$ref: '#/components/schemas/Timestamp'}
|
|
InitializeDocumentUploadRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [name, classification, mimeType, sizeBytes]
|
|
properties:
|
|
name: {type: string, minLength: 1, maxLength: 255}
|
|
categoryId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
classification: {$ref: '#/components/schemas/DocumentClassification'}
|
|
retentionPolicyId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
mimeType: {type: string, minLength: 1, maxLength: 255}
|
|
sizeBytes: {type: integer, minimum: 1, maximum: 5368709120}
|
|
contentHash: {type: [string, 'null'], pattern: '^sha256:[a-f0-9]{64}$'}
|
|
InitializeDocumentVersionRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [mimeType, sizeBytes]
|
|
properties:
|
|
mimeType: {type: string, minLength: 1, maxLength: 255}
|
|
sizeBytes: {type: integer, minimum: 1, maximum: 5368709120}
|
|
contentHash: {type: [string, 'null'], pattern: '^sha256:[a-f0-9]{64}$'}
|
|
UpdateDocumentRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
minProperties: 1
|
|
properties:
|
|
name: {type: string, minLength: 1, maxLength: 255}
|
|
categoryId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
classification: {$ref: '#/components/schemas/DocumentClassification'}
|
|
retentionPolicyId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
CompleteDocumentUploadRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [documentVersionId, contentHash]
|
|
properties:
|
|
documentVersionId: {$ref: '#/components/schemas/Uuid'}
|
|
contentHash: {type: string, pattern: '^sha256:[a-f0-9]{64}$'}
|
|
InitializeDocumentUploadData:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [documentId, documentVersionId, uploadUrl, expiresAt]
|
|
properties:
|
|
documentId: {$ref: '#/components/schemas/Uuid'}
|
|
documentVersionId: {$ref: '#/components/schemas/Uuid'}
|
|
uploadUrl: {type: string, format: uri}
|
|
requiredHeaders: {type: object, additionalProperties: {type: string}}
|
|
expiresAt: {$ref: '#/components/schemas/Timestamp'}
|
|
InitializeDocumentUploadResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/InitializeDocumentUploadData'}}
|
|
InitializeMultipartUploadData:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [documentId, documentVersionId, uploadId, recommendedPartSizeBytes, expiresAt]
|
|
properties:
|
|
documentId: {$ref: '#/components/schemas/Uuid'}
|
|
documentVersionId: {$ref: '#/components/schemas/Uuid'}
|
|
uploadId: {$ref: '#/components/schemas/Uuid'}
|
|
recommendedPartSizeBytes: {type: integer, minimum: 5242880}
|
|
expiresAt: {$ref: '#/components/schemas/Timestamp'}
|
|
InitializeMultipartUploadResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/InitializeMultipartUploadData'}}
|
|
MultipartPartUrlsRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [partNumbers]
|
|
properties:
|
|
partNumbers:
|
|
type: array
|
|
minItems: 1
|
|
maxItems: 100
|
|
uniqueItems: true
|
|
items: {type: integer, minimum: 1, maximum: 10000}
|
|
MultipartPartUploadUrl:
|
|
type: object
|
|
required: [partNumber, uploadUrl, expiresAt]
|
|
properties:
|
|
partNumber: {type: integer, minimum: 1}
|
|
uploadUrl: {type: string, format: uri}
|
|
expiresAt: {$ref: '#/components/schemas/Timestamp'}
|
|
MultipartPartUrlsResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {type: array, items: {$ref: '#/components/schemas/MultipartPartUploadUrl'}}}
|
|
CompletedMultipartPart:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [partNumber, etag]
|
|
properties:
|
|
partNumber: {type: integer, minimum: 1}
|
|
etag: {type: string, minLength: 1, maxLength: 200}
|
|
CompleteMultipartUploadRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [parts, contentHash]
|
|
properties:
|
|
parts:
|
|
type: array
|
|
minItems: 1
|
|
maxItems: 10000
|
|
items: {$ref: '#/components/schemas/CompletedMultipartPart'}
|
|
contentHash: {type: string, pattern: '^sha256:[a-f0-9]{64}$'}
|
|
CreateDocumentDownloadRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
properties:
|
|
documentVersionId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}], description: Defaults to the current clean version.}
|
|
DocumentDownloadData:
|
|
type: object
|
|
required: [downloadUrl, expiresAt]
|
|
properties:
|
|
downloadUrl: {type: string, format: uri}
|
|
expiresAt: {$ref: '#/components/schemas/Timestamp'}
|
|
DocumentDownloadResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/DocumentDownloadData'}}
|
|
DocumentResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/Document'}}
|
|
DocumentCollectionResponse:
|
|
type: object
|
|
required: [data, meta]
|
|
properties:
|
|
data: {type: array, items: {$ref: '#/components/schemas/Document'}}
|
|
meta: {$ref: '#/components/schemas/CollectionMeta'}
|
|
DocumentVersionResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/DocumentVersion'}}
|
|
DocumentVersionCollectionResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {type: array, items: {$ref: '#/components/schemas/DocumentVersion'}}}
|
|
EngineeringProjectDocumentLink:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, projectId, documentId, category, linkedByUserId, linkedAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
projectId: {$ref: '#/components/schemas/Uuid'}
|
|
documentId: {$ref: '#/components/schemas/Uuid'}
|
|
category: {type: string, minLength: 1, maxLength: 100}
|
|
document: {$ref: '#/components/schemas/Document'}
|
|
linkedByUserId: {$ref: '#/components/schemas/Uuid'}
|
|
linkedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
unlinkedAt: {type: [string, 'null'], format: date-time}
|
|
LinkEngineeringProjectDocumentRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [documentId, category]
|
|
properties:
|
|
documentId: {$ref: '#/components/schemas/Uuid'}
|
|
category: {type: string, minLength: 1, maxLength: 100}
|
|
EngineeringProjectDocumentResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/EngineeringProjectDocumentLink'}}
|
|
EngineeringProjectDocumentCollectionResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {type: array, items: {$ref: '#/components/schemas/EngineeringProjectDocumentLink'}}}
|
|
EngineeringDesignStatus:
|
|
type: string
|
|
enum: [draft, under_review, changes_requested, approved, rejected, cancelled, withdrawn, superseded]
|
|
EngineeringDesignAssignmentRole:
|
|
type: string
|
|
enum: [owner, preparer, reviewer, contributor]
|
|
EngineeringDesignDocumentRole:
|
|
type: string
|
|
enum: [primary_drawing, calculation, supporting_document, specification, attachment]
|
|
description: Domain-specific registry independent from specification document roles.
|
|
EngineeringDesignReviewStatus:
|
|
type: string
|
|
enum: [approved, changes_requested, rejected]
|
|
EngineeringDesign:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, projectId, designNumber, title, discipline, status, ownerUserId, preparedByUserId, currentVersionId, version, createdAt, updatedAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
projectId: {$ref: '#/components/schemas/Uuid'}
|
|
designNumber: {type: string, minLength: 1, maxLength: 64}
|
|
title: {type: string, minLength: 1, maxLength: 300}
|
|
description: {type: [string, 'null'], maxLength: 10000}
|
|
discipline: {type: string, minLength: 1, maxLength: 100}
|
|
status: {$ref: '#/components/schemas/EngineeringDesignStatus'}
|
|
ownerUserId: {$ref: '#/components/schemas/Uuid'}
|
|
preparedByUserId: {$ref: '#/components/schemas/Uuid'}
|
|
currentVersionId: {$ref: '#/components/schemas/Uuid'}
|
|
approvedVersionId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
approvedByUserId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
approvedAt: {type: [string, 'null'], format: date-time}
|
|
supersededByDesignId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
version: {type: integer, minimum: 1}
|
|
createdAt: {$ref: '#/components/schemas/Timestamp'}
|
|
updatedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
CreateEngineeringDesignRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [designNumber, title, discipline, ownerUserId, preparedByUserId]
|
|
properties:
|
|
designNumber: {type: string, minLength: 1, maxLength: 64}
|
|
title: {type: string, minLength: 1, maxLength: 300}
|
|
description: {type: [string, 'null'], maxLength: 10000}
|
|
discipline: {type: string, minLength: 1, maxLength: 100}
|
|
ownerUserId: {$ref: '#/components/schemas/Uuid'}
|
|
preparedByUserId: {$ref: '#/components/schemas/Uuid'}
|
|
UpdateEngineeringDesignRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
minProperties: 1
|
|
properties:
|
|
title: {type: string, minLength: 1, maxLength: 300}
|
|
description: {type: [string, 'null'], maxLength: 10000}
|
|
discipline: {type: string, minLength: 1, maxLength: 100}
|
|
EngineeringDesignResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/EngineeringDesign'}}
|
|
EngineeringDesignCollectionResponse:
|
|
type: object
|
|
required: [data, meta]
|
|
properties:
|
|
data: {type: array, items: {$ref: '#/components/schemas/EngineeringDesign'}}
|
|
meta: {$ref: '#/components/schemas/CollectionMeta'}
|
|
OptionalDesignReasonCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
properties:
|
|
reason: {type: string, minLength: 3, maxLength: 1000}
|
|
DesignReasonCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [reason]
|
|
properties:
|
|
reason: {type: string, minLength: 3, maxLength: 1000}
|
|
DesignDecisionCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [designVersionId, reason]
|
|
properties:
|
|
designVersionId: {$ref: '#/components/schemas/Uuid'}
|
|
reason: {type: string, minLength: 3, maxLength: 2000}
|
|
ApproveDesignCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [designVersionId, attestation]
|
|
properties:
|
|
designVersionId: {$ref: '#/components/schemas/Uuid'}
|
|
attestation: {type: string, minLength: 10, maxLength: 2000}
|
|
SupersedeDesignCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [replacementDesignId, reason]
|
|
properties:
|
|
replacementDesignId: {$ref: '#/components/schemas/Uuid'}
|
|
reason: {type: string, minLength: 3, maxLength: 1000}
|
|
EngineeringDesignAssignment:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, designId, userId, assignmentRole, assignedByUserId, assignedAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
designId: {$ref: '#/components/schemas/Uuid'}
|
|
userId: {$ref: '#/components/schemas/Uuid'}
|
|
assignmentRole: {$ref: '#/components/schemas/EngineeringDesignAssignmentRole'}
|
|
notes: {type: [string, 'null'], maxLength: 2000}
|
|
assignedByUserId: {$ref: '#/components/schemas/Uuid'}
|
|
assignedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
unassignedAt: {type: [string, 'null'], format: date-time}
|
|
AssignEngineeringDesignRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [userId, assignmentRole]
|
|
properties:
|
|
userId: {$ref: '#/components/schemas/Uuid'}
|
|
assignmentRole: {$ref: '#/components/schemas/EngineeringDesignAssignmentRole'}
|
|
notes: {type: [string, 'null'], maxLength: 2000}
|
|
UnassignEngineeringDesignRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [assignmentId]
|
|
properties:
|
|
assignmentId: {$ref: '#/components/schemas/Uuid'}
|
|
reason: {type: [string, 'null'], maxLength: 1000}
|
|
EngineeringDesignAssignmentResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/EngineeringDesignAssignment'}}
|
|
EngineeringDesignAssignmentCollectionResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {type: array, items: {$ref: '#/components/schemas/EngineeringDesignAssignment'}}}
|
|
EngineeringDesignVersion:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, designId, versionNumber, createdByUserId, createdAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
designId: {$ref: '#/components/schemas/Uuid'}
|
|
versionNumber: {type: integer, minimum: 1}
|
|
changeSummary: {type: [string, 'null'], maxLength: 2000}
|
|
createdByUserId: {$ref: '#/components/schemas/Uuid'}
|
|
createdAt: {$ref: '#/components/schemas/Timestamp'}
|
|
CreateEngineeringDesignVersionRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
properties:
|
|
changeSummary: {type: [string, 'null'], maxLength: 2000}
|
|
EngineeringDesignVersionResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/EngineeringDesignVersion'}}
|
|
EngineeringDesignVersionCollectionResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {type: array, items: {$ref: '#/components/schemas/EngineeringDesignVersion'}}}
|
|
EngineeringDesignVersionDocument:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, designVersionId, documentId, documentRole, linkedByUserId, linkedAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
designVersionId: {$ref: '#/components/schemas/Uuid'}
|
|
documentId: {$ref: '#/components/schemas/Uuid'}
|
|
documentRole: {$ref: '#/components/schemas/EngineeringDesignDocumentRole'}
|
|
document: {$ref: '#/components/schemas/Document'}
|
|
linkedByUserId: {$ref: '#/components/schemas/Uuid'}
|
|
linkedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
unlinkedAt: {type: [string, 'null'], format: date-time}
|
|
LinkEngineeringDesignVersionDocumentRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [documentId, documentRole]
|
|
properties:
|
|
documentId: {$ref: '#/components/schemas/Uuid'}
|
|
documentRole: {$ref: '#/components/schemas/EngineeringDesignDocumentRole'}
|
|
EngineeringDesignVersionDocumentResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/EngineeringDesignVersionDocument'}}
|
|
EngineeringDesignVersionDocumentCollectionResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {type: array, items: {$ref: '#/components/schemas/EngineeringDesignVersionDocument'}}}
|
|
EngineeringDesignReview:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, designId, designVersionId, reviewerUserId, status, comments, reviewedAt, createdAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
designId: {$ref: '#/components/schemas/Uuid'}
|
|
designVersionId: {$ref: '#/components/schemas/Uuid'}
|
|
reviewerUserId: {$ref: '#/components/schemas/Uuid'}
|
|
status: {$ref: '#/components/schemas/EngineeringDesignReviewStatus'}
|
|
comments: {type: string, minLength: 1, maxLength: 10000}
|
|
reviewedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
createdAt: {$ref: '#/components/schemas/Timestamp'}
|
|
CreateEngineeringDesignReviewRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [designVersionId, status, comments]
|
|
properties:
|
|
designVersionId: {$ref: '#/components/schemas/Uuid'}
|
|
status: {$ref: '#/components/schemas/EngineeringDesignReviewStatus'}
|
|
comments: {type: string, minLength: 1, maxLength: 10000}
|
|
EngineeringDesignReviewResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/EngineeringDesignReview'}}
|
|
EngineeringDesignReviewCollectionResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {type: array, items: {$ref: '#/components/schemas/EngineeringDesignReview'}}}
|
|
EngineeringInspectionStatus:
|
|
type: string
|
|
enum: [draft, scheduled, in_progress, completed, cancelled]
|
|
EngineeringInspectionOutcome:
|
|
type: string
|
|
enum: [passed, passed_with_observations, followup_required, failed]
|
|
EngineeringInspectionFindingSeverity:
|
|
type: string
|
|
enum: [observation, minor, major, critical]
|
|
EngineeringInspectionFindingStatus:
|
|
type: string
|
|
enum: [open, in_progress, resolved, accepted_risk]
|
|
EngineeringInspection:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, projectId, siteId, inspectionType, inspectorUserId, status, version, createdAt, updatedAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
projectId: {$ref: '#/components/schemas/Uuid'}
|
|
siteId: {$ref: '#/components/schemas/Uuid'}
|
|
inspectionType: {type: string, minLength: 1, maxLength: 100, description: Controlled application registry key.}
|
|
inspectorUserId: {$ref: '#/components/schemas/Uuid'}
|
|
status: {$ref: '#/components/schemas/EngineeringInspectionStatus'}
|
|
outcome: {oneOf: [{$ref: '#/components/schemas/EngineeringInspectionOutcome'}, {type: 'null'}]}
|
|
scheduledAt: {type: [string, 'null'], format: date-time}
|
|
startedAt: {type: [string, 'null'], format: date-time}
|
|
performedAt: {type: [string, 'null'], format: date-time}
|
|
cancelledAt: {type: [string, 'null'], format: date-time}
|
|
summary: {type: [string, 'null'], maxLength: 10000}
|
|
version: {type: integer, minimum: 1}
|
|
createdAt: {$ref: '#/components/schemas/Timestamp'}
|
|
updatedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
CreateEngineeringInspectionRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [siteId, inspectionType, inspectorUserId]
|
|
properties:
|
|
siteId: {$ref: '#/components/schemas/Uuid'}
|
|
inspectionType: {type: string, minLength: 1, maxLength: 100}
|
|
inspectorUserId: {$ref: '#/components/schemas/Uuid'}
|
|
summary: {type: [string, 'null'], maxLength: 10000}
|
|
UpdateEngineeringInspectionRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
minProperties: 1
|
|
properties:
|
|
siteId: {$ref: '#/components/schemas/Uuid'}
|
|
inspectionType: {type: string, minLength: 1, maxLength: 100}
|
|
inspectorUserId: {$ref: '#/components/schemas/Uuid'}
|
|
summary: {type: [string, 'null'], maxLength: 10000}
|
|
EngineeringInspectionResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/EngineeringInspection'}}
|
|
EngineeringInspectionCollectionResponse:
|
|
type: object
|
|
required: [data, meta]
|
|
properties:
|
|
data: {type: array, items: {$ref: '#/components/schemas/EngineeringInspection'}}
|
|
meta: {$ref: '#/components/schemas/CollectionMeta'}
|
|
ScheduleInspectionCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [scheduledAt]
|
|
properties:
|
|
scheduledAt: {$ref: '#/components/schemas/Timestamp'}
|
|
StartInspectionCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
properties:
|
|
startedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
CompleteInspectionCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [outcome, summary]
|
|
properties:
|
|
outcome: {$ref: '#/components/schemas/EngineeringInspectionOutcome'}
|
|
performedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
summary: {type: string, minLength: 1, maxLength: 10000}
|
|
createFollowups: {type: boolean, default: false}
|
|
InspectionReasonCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [reason]
|
|
properties:
|
|
reason: {type: string, minLength: 3, maxLength: 1000}
|
|
OptionalInspectionReasonCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
properties:
|
|
reason: {type: string, minLength: 3, maxLength: 1000}
|
|
EngineeringInspectionDocument:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, inspectionId, documentId, category, linkedByUserId, linkedAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
inspectionId: {$ref: '#/components/schemas/Uuid'}
|
|
documentId: {$ref: '#/components/schemas/Uuid'}
|
|
category: {type: string, enum: [evidence, photo, report, certificate, supporting_document]}
|
|
document: {$ref: '#/components/schemas/Document'}
|
|
linkedByUserId: {$ref: '#/components/schemas/Uuid'}
|
|
linkedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
unlinkedAt: {type: [string, 'null'], format: date-time}
|
|
LinkEngineeringInspectionDocumentRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [documentId, category]
|
|
properties:
|
|
documentId: {$ref: '#/components/schemas/Uuid'}
|
|
category: {type: string, enum: [evidence, photo, report, certificate, supporting_document]}
|
|
EngineeringInspectionDocumentResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/EngineeringInspectionDocument'}}
|
|
EngineeringInspectionDocumentCollectionResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {type: array, items: {$ref: '#/components/schemas/EngineeringInspectionDocument'}}}
|
|
EngineeringInspectionFinding:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, inspectionId, severity, description, status, createdByUserId, version, createdAt, updatedAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
inspectionId: {$ref: '#/components/schemas/Uuid'}
|
|
severity: {$ref: '#/components/schemas/EngineeringInspectionFindingSeverity'}
|
|
description: {type: string, minLength: 1, maxLength: 10000}
|
|
status: {$ref: '#/components/schemas/EngineeringInspectionFindingStatus'}
|
|
remediationOwnerUserId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
targetResolutionDate: {type: [string, 'null'], format: date}
|
|
resolutionSummary: {type: [string, 'null'], maxLength: 10000}
|
|
resolvedAt: {type: [string, 'null'], format: date-time}
|
|
resolvedByUserId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
acceptedRiskReason: {type: [string, 'null'], maxLength: 5000}
|
|
riskReviewDate: {type: [string, 'null'], format: date}
|
|
createdByUserId: {$ref: '#/components/schemas/Uuid'}
|
|
version: {type: integer, minimum: 1}
|
|
createdAt: {$ref: '#/components/schemas/Timestamp'}
|
|
updatedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
CreateEngineeringInspectionFindingRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [severity, description]
|
|
properties:
|
|
severity: {$ref: '#/components/schemas/EngineeringInspectionFindingSeverity'}
|
|
description: {type: string, minLength: 1, maxLength: 10000}
|
|
remediationOwnerUserId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
targetResolutionDate: {type: [string, 'null'], format: date}
|
|
UpdateEngineeringInspectionFindingRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
minProperties: 1
|
|
properties:
|
|
severity: {$ref: '#/components/schemas/EngineeringInspectionFindingSeverity'}
|
|
description: {type: string, minLength: 1, maxLength: 10000}
|
|
remediationOwnerUserId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
targetResolutionDate: {type: [string, 'null'], format: date}
|
|
ResolveEngineeringFindingCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [resolutionSummary]
|
|
properties:
|
|
resolutionSummary: {type: string, minLength: 3, maxLength: 10000}
|
|
evidenceDocumentIds:
|
|
type: array
|
|
maxItems: 50
|
|
uniqueItems: true
|
|
items: {$ref: '#/components/schemas/Uuid'}
|
|
privilegedSelfVerificationReason: {type: [string, 'null'], minLength: 10, maxLength: 2000}
|
|
AcceptEngineeringFindingRiskCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [reason, reviewDate]
|
|
properties:
|
|
reason: {type: string, minLength: 10, maxLength: 5000}
|
|
reviewDate: {$ref: '#/components/schemas/Date'}
|
|
approvingUserId: {$ref: '#/components/schemas/Uuid'}
|
|
EngineeringInspectionFindingResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/EngineeringInspectionFinding'}}
|
|
EngineeringInspectionFindingCollectionResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {type: array, items: {$ref: '#/components/schemas/EngineeringInspectionFinding'}}}
|
|
EngineeringInspectionFollowupType:
|
|
type: string
|
|
enum: [corrective_task, followup_inspection, both]
|
|
EngineeringInspectionFollowupStatus:
|
|
type: string
|
|
enum: [open, in_progress, completed, cancelled]
|
|
EngineeringInspectionFollowup:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, inspectionId, followupType, status, createdByUserId, version, createdAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
inspectionId: {$ref: '#/components/schemas/Uuid'}
|
|
followupType: {$ref: '#/components/schemas/EngineeringInspectionFollowupType'}
|
|
linkedTaskId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
linkedInspectionId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
status: {$ref: '#/components/schemas/EngineeringInspectionFollowupStatus'}
|
|
createdByUserId: {$ref: '#/components/schemas/Uuid'}
|
|
version: {type: integer, minimum: 1}
|
|
createdAt: {$ref: '#/components/schemas/Timestamp'}
|
|
completedAt: {type: [string, 'null'], format: date-time}
|
|
cancelledAt: {type: [string, 'null'], format: date-time}
|
|
CreateEngineeringInspectionFollowupRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [followupType]
|
|
properties:
|
|
followupType: {$ref: '#/components/schemas/EngineeringInspectionFollowupType'}
|
|
linkedTaskId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
linkedInspectionId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
EngineeringInspectionFollowupResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/EngineeringInspectionFollowup'}}
|
|
EngineeringInspectionFollowupCollectionResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {type: array, items: {$ref: '#/components/schemas/EngineeringInspectionFollowup'}}}
|
|
EngineeringSpecificationStatus:
|
|
type: string
|
|
enum: [draft, active, superseded, archived]
|
|
EngineeringSpecificationDocumentRole:
|
|
type: string
|
|
enum: [primary, attachment, supporting_document]
|
|
description: Specification-specific registry; independent from design-version document roles.
|
|
EngineeringSpecification:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, projectId, specificationNumber, title, status, version, createdAt, updatedAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
projectId: {$ref: '#/components/schemas/Uuid'}
|
|
specificationNumber: {type: string, minLength: 1, maxLength: 64}
|
|
title: {type: string, minLength: 1, maxLength: 300}
|
|
description: {type: [string, 'null'], maxLength: 10000}
|
|
status: {$ref: '#/components/schemas/EngineeringSpecificationStatus'}
|
|
supersededBySpecificationId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
archivedFromStatus:
|
|
oneOf:
|
|
- type: string
|
|
enum: [draft, active]
|
|
- type: 'null'
|
|
archivedAt: {type: [string, 'null'], format: date-time}
|
|
archivedByUserId: {oneOf: [{$ref: '#/components/schemas/Uuid'}, {type: 'null'}]}
|
|
version: {type: integer, minimum: 1}
|
|
createdAt: {$ref: '#/components/schemas/Timestamp'}
|
|
updatedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
CreateEngineeringSpecificationRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [specificationNumber, title]
|
|
properties:
|
|
specificationNumber: {type: string, minLength: 1, maxLength: 64}
|
|
title: {type: string, minLength: 1, maxLength: 300}
|
|
description: {type: [string, 'null'], maxLength: 10000}
|
|
UpdateEngineeringSpecificationRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
minProperties: 1
|
|
properties:
|
|
title: {type: string, minLength: 1, maxLength: 300}
|
|
description: {type: [string, 'null'], maxLength: 10000}
|
|
EngineeringSpecificationResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/EngineeringSpecification'}}
|
|
EngineeringSpecificationCollectionResponse:
|
|
type: object
|
|
required: [data, meta]
|
|
properties:
|
|
data: {type: array, items: {$ref: '#/components/schemas/EngineeringSpecification'}}
|
|
meta: {$ref: '#/components/schemas/CollectionMeta'}
|
|
SpecificationReasonCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [reason]
|
|
properties:
|
|
reason: {type: string, minLength: 3, maxLength: 1000}
|
|
OptionalSpecificationReasonCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
properties:
|
|
reason: {type: string, minLength: 3, maxLength: 1000}
|
|
SupersedeSpecificationCommand:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [supersededBySpecificationId, reason]
|
|
properties:
|
|
supersededBySpecificationId: {$ref: '#/components/schemas/Uuid'}
|
|
reason: {type: string, minLength: 3, maxLength: 1000}
|
|
EngineeringSpecificationDocument:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, specificationId, documentId, documentRole, linkedByUserId, linkedAt]
|
|
properties:
|
|
id: {$ref: '#/components/schemas/Uuid'}
|
|
organizationId: {$ref: '#/components/schemas/Uuid'}
|
|
specificationId: {$ref: '#/components/schemas/Uuid'}
|
|
documentId: {$ref: '#/components/schemas/Uuid'}
|
|
documentRole: {$ref: '#/components/schemas/EngineeringSpecificationDocumentRole'}
|
|
document: {$ref: '#/components/schemas/Document'}
|
|
linkedByUserId: {$ref: '#/components/schemas/Uuid'}
|
|
linkedAt: {$ref: '#/components/schemas/Timestamp'}
|
|
unlinkedAt: {type: [string, 'null'], format: date-time}
|
|
LinkEngineeringSpecificationDocumentRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [documentId, documentRole]
|
|
properties:
|
|
documentId: {$ref: '#/components/schemas/Uuid'}
|
|
documentRole: {$ref: '#/components/schemas/EngineeringSpecificationDocumentRole'}
|
|
EngineeringSpecificationDocumentResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {$ref: '#/components/schemas/EngineeringSpecificationDocument'}}
|
|
EngineeringSpecificationDocumentCollectionResponse:
|
|
type: object
|
|
required: [data]
|
|
properties: {data: {type: array, items: {$ref: '#/components/schemas/EngineeringSpecificationDocument'}}}
|
|
Profession:
|
|
type: string
|
|
enum: [engineering, legal, healthcare]
|
|
UserStatus:
|
|
type: string
|
|
enum: [active, inactive, pending_verification]
|
|
OrganizationStatus:
|
|
type: string
|
|
enum: [active, suspended, pending_deletion]
|
|
MembershipStatus:
|
|
type: string
|
|
enum: [active, inactive, pending]
|
|
InvitationStatus:
|
|
type: string
|
|
enum: [pending, accepted, revoked, expired]
|
|
description: Derived from invitation timestamps and expiry.
|
|
RoleStatus:
|
|
type: string
|
|
enum: [active, inactive]
|
|
description: Inactive roles retain assignments for history but grant no permissions and cannot be newly assigned.
|
|
EngineeringClientType:
|
|
type: string
|
|
enum: [corporate, government, individual]
|
|
EngineeringClientStatus:
|
|
type: string
|
|
enum: [active, archived]
|
|
EngineeringContactType:
|
|
type: string
|
|
enum: [technical, billing, executive, site, contract, other]
|
|
EngineeringContactStatus:
|
|
type: string
|
|
enum: [active, archived]
|
|
EngineeringProjectStatus:
|
|
type: string
|
|
enum: [draft, active, closed, archived]
|
|
EngineeringProjectRestorableStatus:
|
|
type: string
|
|
enum: [draft, closed]
|
|
EngineeringDiscipline:
|
|
type: string
|
|
enum:
|
|
- civil
|
|
- structural
|
|
- mechanical
|
|
- electrical
|
|
- geotechnical
|
|
- environmental
|
|
- transportation
|
|
- water_resources
|
|
- surveying
|
|
- multidisciplinary
|
|
- other
|
|
EngineeringProjectMemberRole:
|
|
type: string
|
|
enum: [engineer, designer, reviewer, inspector, viewer, contractor]
|
|
description: Project manager is intentionally excluded; `projectManagerUserId` is authoritative.
|
|
EngineeringProjectMemberStatus:
|
|
type: string
|
|
enum: [active, left]
|
|
description: Derived from whether `leftAt` is null.
|
|
EngineeringTaskStatus:
|
|
type: string
|
|
enum: [todo, in_progress, completed, cancelled]
|
|
EngineeringTaskPriority:
|
|
type: string
|
|
enum: [low, medium, high, urgent]
|
|
BatchExecutionMode:
|
|
type: string
|
|
enum: [atomic, partial]
|
|
|
|
Problem:
|
|
type: object
|
|
additionalProperties: true
|
|
required: [type, title, status, code, requestId]
|
|
properties:
|
|
type:
|
|
type: string
|
|
format: uri-reference
|
|
title:
|
|
type: string
|
|
status:
|
|
type: integer
|
|
minimum: 400
|
|
maximum: 599
|
|
detail:
|
|
type: string
|
|
instance:
|
|
type: string
|
|
format: uri-reference
|
|
code:
|
|
type: string
|
|
pattern: '^[A-Z][A-Z0-9_]+$'
|
|
description: Stable machine-readable application error code.
|
|
requestId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
errors:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/FieldError'
|
|
FieldError:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [field, code, message]
|
|
properties:
|
|
field:
|
|
type: string
|
|
code:
|
|
type: string
|
|
message:
|
|
type: string
|
|
|
|
PaginationMeta:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [nextCursor, hasMore]
|
|
properties:
|
|
nextCursor:
|
|
type: [string, 'null']
|
|
hasMore:
|
|
type: boolean
|
|
CollectionMeta:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [pagination]
|
|
properties:
|
|
pagination:
|
|
$ref: '#/components/schemas/PaginationMeta'
|
|
|
|
RegisterRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [email, password, firstName, lastName]
|
|
properties:
|
|
email:
|
|
$ref: '#/components/schemas/Email'
|
|
password:
|
|
type: string
|
|
minLength: 12
|
|
maxLength: 128
|
|
writeOnly: true
|
|
firstName:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
lastName:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
LoginRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [email, password]
|
|
properties:
|
|
email:
|
|
$ref: '#/components/schemas/Email'
|
|
password:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 128
|
|
writeOnly: true
|
|
RefreshTokenRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [refreshToken]
|
|
properties:
|
|
refreshToken:
|
|
type: string
|
|
minLength: 32
|
|
maxLength: 4096
|
|
writeOnly: true
|
|
TokenPair:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [accessToken, refreshToken, tokenType, expiresIn, sessionId]
|
|
properties:
|
|
accessToken:
|
|
type: string
|
|
readOnly: true
|
|
refreshToken:
|
|
type: string
|
|
readOnly: true
|
|
tokenType:
|
|
type: string
|
|
const: Bearer
|
|
expiresIn:
|
|
type: integer
|
|
minimum: 1
|
|
description: Access-token lifetime in seconds.
|
|
sessionId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
TokenPairResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/TokenPair'
|
|
|
|
User:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, email, firstName, lastName, status, version, createdAt, updatedAt]
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
email:
|
|
$ref: '#/components/schemas/Email'
|
|
firstName:
|
|
type: string
|
|
lastName:
|
|
type: string
|
|
phone:
|
|
type: [string, 'null']
|
|
maxLength: 32
|
|
avatarUrl:
|
|
type: [string, 'null']
|
|
format: uri
|
|
status:
|
|
$ref: '#/components/schemas/UserStatus'
|
|
version:
|
|
type: integer
|
|
minimum: 1
|
|
createdAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
updatedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
UserResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/User'
|
|
UpdateCurrentUserRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
minProperties: 1
|
|
properties:
|
|
firstName:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
lastName:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
phone:
|
|
type: [string, 'null']
|
|
maxLength: 32
|
|
avatarUrl:
|
|
type: [string, 'null']
|
|
format: uri
|
|
|
|
Session:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, current, createdAt, lastActiveAt, expiresAt]
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
current:
|
|
type: boolean
|
|
deviceName:
|
|
type: [string, 'null']
|
|
maxLength: 200
|
|
ipAddress:
|
|
type: [string, 'null']
|
|
description: Redacted or omitted according to privacy policy.
|
|
userAgent:
|
|
type: [string, 'null']
|
|
maxLength: 512
|
|
createdAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
lastActiveAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
expiresAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
revokedAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
SessionCollectionResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data, meta]
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Session'
|
|
meta:
|
|
$ref: '#/components/schemas/CollectionMeta'
|
|
|
|
Organization:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, name, slug, status, countryCode, timezone, currencyCode, professions, version, createdAt, updatedAt]
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
slug:
|
|
type: string
|
|
pattern: '^[a-z0-9]+(?:-[a-z0-9]+)*$'
|
|
minLength: 3
|
|
maxLength: 80
|
|
status:
|
|
$ref: '#/components/schemas/OrganizationStatus'
|
|
countryCode:
|
|
$ref: '#/components/schemas/CountryCode'
|
|
timezone:
|
|
type: string
|
|
description: IANA time-zone identifier.
|
|
examples: [Africa/Casablanca]
|
|
currencyCode:
|
|
$ref: '#/components/schemas/CurrencyCode'
|
|
professions:
|
|
type: array
|
|
minItems: 1
|
|
uniqueItems: true
|
|
items:
|
|
$ref: '#/components/schemas/Profession'
|
|
version:
|
|
type: integer
|
|
minimum: 1
|
|
createdAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
updatedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
CreateOrganizationRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [name, slug, countryCode, timezone, currencyCode, professions]
|
|
properties:
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
slug:
|
|
type: string
|
|
pattern: '^[a-z0-9]+(?:-[a-z0-9]+)*$'
|
|
minLength: 3
|
|
maxLength: 80
|
|
countryCode:
|
|
$ref: '#/components/schemas/CountryCode'
|
|
timezone:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
currencyCode:
|
|
$ref: '#/components/schemas/CurrencyCode'
|
|
professions:
|
|
type: array
|
|
minItems: 1
|
|
uniqueItems: true
|
|
items:
|
|
$ref: '#/components/schemas/Profession'
|
|
UpdateOrganizationRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
minProperties: 1
|
|
description: Status and enabled professions change through separately authorized commands.
|
|
properties:
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
slug:
|
|
type: string
|
|
pattern: '^[a-z0-9]+(?:-[a-z0-9]+)*$'
|
|
minLength: 3
|
|
maxLength: 80
|
|
countryCode:
|
|
$ref: '#/components/schemas/CountryCode'
|
|
timezone:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
currencyCode:
|
|
$ref: '#/components/schemas/CurrencyCode'
|
|
OrganizationResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/Organization'
|
|
OrganizationCollectionResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data, meta]
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Organization'
|
|
meta:
|
|
$ref: '#/components/schemas/CollectionMeta'
|
|
|
|
Invitation:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, email, roleIds, status, invitedByUserId, expiresAt, version, createdAt]
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
organizationId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
email:
|
|
$ref: '#/components/schemas/Email'
|
|
roleIds:
|
|
type: array
|
|
minItems: 1
|
|
uniqueItems: true
|
|
items:
|
|
$ref: '#/components/schemas/Uuid'
|
|
status:
|
|
$ref: '#/components/schemas/InvitationStatus'
|
|
invitedByUserId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
expiresAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
acceptedAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
revokedAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
version:
|
|
type: integer
|
|
minimum: 1
|
|
createdAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
CreateInvitationRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [email, roleIds]
|
|
properties:
|
|
email:
|
|
$ref: '#/components/schemas/Email'
|
|
roleIds:
|
|
type: array
|
|
minItems: 1
|
|
maxItems: 20
|
|
uniqueItems: true
|
|
items:
|
|
$ref: '#/components/schemas/Uuid'
|
|
expiresInDays:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 30
|
|
default: 7
|
|
AcceptInvitationRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [token]
|
|
properties:
|
|
token:
|
|
type: string
|
|
minLength: 32
|
|
maxLength: 4096
|
|
writeOnly: true
|
|
InvitationResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/Invitation'
|
|
InvitationCollectionResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data, meta]
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Invitation'
|
|
meta:
|
|
$ref: '#/components/schemas/CollectionMeta'
|
|
|
|
Membership:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, user, status, roles, joinedAt, version, createdAt, updatedAt]
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
organizationId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
user:
|
|
$ref: '#/components/schemas/UserSummary'
|
|
status:
|
|
$ref: '#/components/schemas/MembershipStatus'
|
|
roles:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/RoleSummary'
|
|
joinedAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
version:
|
|
type: integer
|
|
minimum: 1
|
|
createdAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
updatedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
UserSummary:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, email, firstName, lastName]
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
email:
|
|
$ref: '#/components/schemas/Email'
|
|
firstName:
|
|
type: string
|
|
lastName:
|
|
type: string
|
|
MembershipResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/Membership'
|
|
MembershipCollectionResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data, meta]
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Membership'
|
|
meta:
|
|
$ref: '#/components/schemas/CollectionMeta'
|
|
ReplaceMembershipRolesRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [roleIds]
|
|
properties:
|
|
roleIds:
|
|
type: array
|
|
minItems: 1
|
|
maxItems: 20
|
|
uniqueItems: true
|
|
items:
|
|
$ref: '#/components/schemas/Uuid'
|
|
ReasonRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
properties:
|
|
reason:
|
|
type: string
|
|
maxLength: 500
|
|
|
|
Role:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, organizationId, name, slug, description, status, isSystem, permissions, version, createdAt, updatedAt]
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
organizationId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
slug:
|
|
type: string
|
|
pattern: '^[a-z0-9]+(?:_[a-z0-9]+)*$'
|
|
minLength: 2
|
|
maxLength: 100
|
|
description:
|
|
type: [string, 'null']
|
|
maxLength: 500
|
|
status:
|
|
$ref: '#/components/schemas/RoleStatus'
|
|
isSystem:
|
|
type: boolean
|
|
permissions:
|
|
type: array
|
|
uniqueItems: true
|
|
items:
|
|
$ref: '#/components/schemas/PermissionGrant'
|
|
version:
|
|
type: integer
|
|
minimum: 1
|
|
createdAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
updatedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
RoleSummary:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, name, slug, status, isSystem]
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
name:
|
|
type: string
|
|
slug:
|
|
type: string
|
|
status:
|
|
$ref: '#/components/schemas/RoleStatus'
|
|
isSystem:
|
|
type: boolean
|
|
CreateRoleRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [name, slug, permissions]
|
|
properties:
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
slug:
|
|
type: string
|
|
pattern: '^[a-z0-9]+(?:_[a-z0-9]+)*$'
|
|
minLength: 2
|
|
maxLength: 100
|
|
description:
|
|
type: [string, 'null']
|
|
maxLength: 500
|
|
permissions:
|
|
type: array
|
|
maxItems: 200
|
|
uniqueItems: true
|
|
items:
|
|
$ref: '#/components/schemas/PermissionGrant'
|
|
UpdateRoleRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
minProperties: 1
|
|
properties:
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
description:
|
|
type: [string, 'null']
|
|
maxLength: 500
|
|
permissions:
|
|
type: array
|
|
maxItems: 200
|
|
uniqueItems: true
|
|
items:
|
|
$ref: '#/components/schemas/PermissionGrant'
|
|
RoleResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/Role'
|
|
RoleCollectionResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data, meta]
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Role'
|
|
meta:
|
|
$ref: '#/components/schemas/CollectionMeta'
|
|
|
|
Permission:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, code, name, scopeOptions]
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
code:
|
|
type: string
|
|
pattern: '^[a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+$'
|
|
examples: [engineering.projects.create]
|
|
name:
|
|
type: string
|
|
description:
|
|
type: [string, 'null']
|
|
profession:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Profession'
|
|
- type: 'null'
|
|
scopeOptions:
|
|
type: array
|
|
minItems: 1
|
|
uniqueItems: true
|
|
items:
|
|
type: string
|
|
enum: [assigned, organization]
|
|
PermissionGrant:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [permissionId, scope]
|
|
properties:
|
|
permissionId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
scope:
|
|
type: string
|
|
enum: [assigned, organization]
|
|
description: The selected scope must be allowed by the referenced permission.
|
|
PermissionCollectionResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data, meta]
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Permission'
|
|
meta:
|
|
$ref: '#/components/schemas/CollectionMeta'
|
|
|
|
EngineeringClient:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- id
|
|
- organizationId
|
|
- clientType
|
|
- displayName
|
|
- legalName
|
|
- status
|
|
- archivedAt
|
|
- archivedByUserId
|
|
- version
|
|
- createdAt
|
|
- updatedAt
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
organizationId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
clientType:
|
|
$ref: '#/components/schemas/EngineeringClientType'
|
|
displayName:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
legalName:
|
|
type: [string, 'null']
|
|
minLength: 1
|
|
maxLength: 300
|
|
status:
|
|
$ref: '#/components/schemas/EngineeringClientStatus'
|
|
archivedAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
archivedByUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
version:
|
|
type: integer
|
|
minimum: 1
|
|
createdAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
updatedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
CreateEngineeringClientRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [clientType, displayName]
|
|
properties:
|
|
clientType:
|
|
$ref: '#/components/schemas/EngineeringClientType'
|
|
displayName:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
legalName:
|
|
type: [string, 'null']
|
|
minLength: 1
|
|
maxLength: 300
|
|
allOf:
|
|
- if:
|
|
properties:
|
|
clientType:
|
|
enum: [corporate, government]
|
|
required: [clientType]
|
|
then:
|
|
required: [legalName]
|
|
properties:
|
|
legalName:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 300
|
|
description: Corporate and government clients require a non-null legal name.
|
|
UpdateEngineeringClientRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
minProperties: 1
|
|
properties:
|
|
clientType:
|
|
$ref: '#/components/schemas/EngineeringClientType'
|
|
displayName:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
legalName:
|
|
type: [string, 'null']
|
|
minLength: 1
|
|
maxLength: 300
|
|
description: The resulting corporate or government client must have a non-null legal name.
|
|
EngineeringClientResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/EngineeringClient'
|
|
EngineeringClientCollectionResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data, meta]
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/EngineeringClient'
|
|
meta:
|
|
$ref: '#/components/schemas/CollectionMeta'
|
|
|
|
EngineeringClientContact:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- id
|
|
- organizationId
|
|
- clientId
|
|
- name
|
|
- title
|
|
- department
|
|
- email
|
|
- phone
|
|
- contactType
|
|
- isPrimary
|
|
- status
|
|
- archivedAt
|
|
- archivedByUserId
|
|
- version
|
|
- createdAt
|
|
- updatedAt
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
organizationId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
clientId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
title:
|
|
type: [string, 'null']
|
|
maxLength: 150
|
|
department:
|
|
type: [string, 'null']
|
|
maxLength: 150
|
|
email:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Email'
|
|
- type: 'null'
|
|
phone:
|
|
type: [string, 'null']
|
|
maxLength: 32
|
|
contactType:
|
|
$ref: '#/components/schemas/EngineeringContactType'
|
|
isPrimary:
|
|
type: boolean
|
|
status:
|
|
$ref: '#/components/schemas/EngineeringContactStatus'
|
|
archivedAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
archivedByUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
version:
|
|
type: integer
|
|
minimum: 1
|
|
createdAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
updatedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
allOf:
|
|
- if:
|
|
properties:
|
|
status:
|
|
const: archived
|
|
required: [status]
|
|
then:
|
|
properties:
|
|
isPrimary:
|
|
const: false
|
|
CreateEngineeringClientContactRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [name, contactType]
|
|
properties:
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
title:
|
|
type: [string, 'null']
|
|
maxLength: 150
|
|
department:
|
|
type: [string, 'null']
|
|
maxLength: 150
|
|
email:
|
|
$ref: '#/components/schemas/Email'
|
|
phone:
|
|
type: string
|
|
minLength: 3
|
|
maxLength: 32
|
|
contactType:
|
|
$ref: '#/components/schemas/EngineeringContactType'
|
|
isPrimary:
|
|
type: boolean
|
|
default: false
|
|
anyOf:
|
|
- required: [email]
|
|
- required: [phone]
|
|
description: At least one of email or phone is required.
|
|
UpdateEngineeringClientContactRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
minProperties: 1
|
|
properties:
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
title:
|
|
type: [string, 'null']
|
|
maxLength: 150
|
|
department:
|
|
type: [string, 'null']
|
|
maxLength: 150
|
|
email:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Email'
|
|
- type: 'null'
|
|
phone:
|
|
type: [string, 'null']
|
|
minLength: 3
|
|
maxLength: 32
|
|
contactType:
|
|
$ref: '#/components/schemas/EngineeringContactType'
|
|
isPrimary:
|
|
type: boolean
|
|
description: The resulting contact must retain at least one of email or phone.
|
|
EngineeringClientContactResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/EngineeringClientContact'
|
|
EngineeringClientContactCollectionResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data, meta]
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/EngineeringClientContact'
|
|
meta:
|
|
$ref: '#/components/schemas/CollectionMeta'
|
|
|
|
EngineeringProject:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- id
|
|
- organizationId
|
|
- clientId
|
|
- projectNumber
|
|
- name
|
|
- description
|
|
- discipline
|
|
- status
|
|
- projectManagerUserId
|
|
- startDate
|
|
- expectedCompletionDate
|
|
- completedDate
|
|
- archivedAt
|
|
- archivedByUserId
|
|
- archivedFromStatus
|
|
- version
|
|
- createdAt
|
|
- updatedAt
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
organizationId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
clientId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
projectNumber:
|
|
type: string
|
|
pattern: '^[A-Za-z0-9][A-Za-z0-9._/-]*$'
|
|
minLength: 1
|
|
maxLength: 100
|
|
description: Immutable, organization-unique human project reference.
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
description:
|
|
type: [string, 'null']
|
|
maxLength: 5000
|
|
discipline:
|
|
$ref: '#/components/schemas/EngineeringDiscipline'
|
|
status:
|
|
$ref: '#/components/schemas/EngineeringProjectStatus'
|
|
projectManagerUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
startDate:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Date'
|
|
- type: 'null'
|
|
expectedCompletionDate:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Date'
|
|
- type: 'null'
|
|
completedDate:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Date'
|
|
- type: 'null'
|
|
archivedAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
archivedByUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
archivedFromStatus:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/EngineeringProjectRestorableStatus'
|
|
- type: 'null'
|
|
version:
|
|
type: integer
|
|
minimum: 1
|
|
createdAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
updatedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
allOf:
|
|
- if:
|
|
properties:
|
|
status:
|
|
enum: [active, closed]
|
|
required: [status]
|
|
then:
|
|
properties:
|
|
projectManagerUserId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
startDate:
|
|
$ref: '#/components/schemas/Date'
|
|
- if:
|
|
properties:
|
|
status:
|
|
const: closed
|
|
required: [status]
|
|
then:
|
|
properties:
|
|
completedDate:
|
|
$ref: '#/components/schemas/Date'
|
|
- if:
|
|
properties:
|
|
status:
|
|
enum: [draft, active]
|
|
required: [status]
|
|
then:
|
|
properties:
|
|
completedDate:
|
|
type: 'null'
|
|
- if:
|
|
properties:
|
|
status:
|
|
const: archived
|
|
required: [status]
|
|
then:
|
|
properties:
|
|
archivedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
archivedByUserId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
archivedFromStatus:
|
|
$ref: '#/components/schemas/EngineeringProjectRestorableStatus'
|
|
else:
|
|
properties:
|
|
archivedAt:
|
|
type: 'null'
|
|
archivedByUserId:
|
|
type: 'null'
|
|
archivedFromStatus:
|
|
type: 'null'
|
|
- if:
|
|
properties:
|
|
status:
|
|
const: archived
|
|
archivedFromStatus:
|
|
const: closed
|
|
required: [status, archivedFromStatus]
|
|
then:
|
|
properties:
|
|
projectManagerUserId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
startDate:
|
|
$ref: '#/components/schemas/Date'
|
|
completedDate:
|
|
$ref: '#/components/schemas/Date'
|
|
- if:
|
|
properties:
|
|
status:
|
|
const: archived
|
|
archivedFromStatus:
|
|
const: draft
|
|
required: [status, archivedFromStatus]
|
|
then:
|
|
properties:
|
|
completedDate:
|
|
type: 'null'
|
|
description: Expected and completed dates may not precede the start date.
|
|
CreateEngineeringProjectRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [clientId, projectNumber, name, discipline]
|
|
properties:
|
|
clientId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
projectNumber:
|
|
type: string
|
|
pattern: '^[A-Za-z0-9][A-Za-z0-9._/-]*$'
|
|
minLength: 1
|
|
maxLength: 100
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
description:
|
|
type: [string, 'null']
|
|
maxLength: 5000
|
|
discipline:
|
|
$ref: '#/components/schemas/EngineeringDiscipline'
|
|
projectManagerUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
startDate:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Date'
|
|
- type: 'null'
|
|
expectedCompletionDate:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Date'
|
|
- type: 'null'
|
|
description: Expected completion date may not precede start date.
|
|
UpdateEngineeringProjectRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
minProperties: 1
|
|
properties:
|
|
clientId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
description:
|
|
type: [string, 'null']
|
|
maxLength: 5000
|
|
discipline:
|
|
$ref: '#/components/schemas/EngineeringDiscipline'
|
|
projectManagerUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
startDate:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Date'
|
|
- type: 'null'
|
|
expectedCompletionDate:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Date'
|
|
- type: 'null'
|
|
description: The resulting dates and manager assignment must satisfy the project's current state rules.
|
|
ActivateEngineeringProjectRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
properties:
|
|
startDate:
|
|
$ref: '#/components/schemas/Date'
|
|
CloseEngineeringProjectRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
properties:
|
|
completedDate:
|
|
$ref: '#/components/schemas/Date'
|
|
reason:
|
|
type: string
|
|
maxLength: 500
|
|
EngineeringProjectResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/EngineeringProject'
|
|
EngineeringProjectCollectionResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data, meta]
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/EngineeringProject'
|
|
meta:
|
|
$ref: '#/components/schemas/CollectionMeta'
|
|
|
|
EngineeringProjectSummary:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- id
|
|
- clientId
|
|
- projectNumber
|
|
- name
|
|
- discipline
|
|
- status
|
|
- projectManagerUserId
|
|
- startDate
|
|
- expectedCompletionDate
|
|
- completedDate
|
|
- version
|
|
- createdAt
|
|
- updatedAt
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
clientId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
projectNumber:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 100
|
|
name:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 200
|
|
discipline:
|
|
$ref: '#/components/schemas/EngineeringDiscipline'
|
|
status:
|
|
$ref: '#/components/schemas/EngineeringProjectStatus'
|
|
projectManagerUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
startDate:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Date'
|
|
- type: 'null'
|
|
expectedCompletionDate:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Date'
|
|
- type: 'null'
|
|
completedDate:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Date'
|
|
- type: 'null'
|
|
version:
|
|
type: integer
|
|
minimum: 1
|
|
createdAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
updatedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
EngineeringProjectSummaryCollectionResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data, meta]
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/EngineeringProjectSummary'
|
|
meta:
|
|
$ref: '#/components/schemas/CollectionMeta'
|
|
|
|
EngineeringClientSummary:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, clientType, displayName, legalName, status]
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
clientType:
|
|
$ref: '#/components/schemas/EngineeringClientType'
|
|
displayName:
|
|
type: string
|
|
legalName:
|
|
type: [string, 'null']
|
|
status:
|
|
$ref: '#/components/schemas/EngineeringClientStatus'
|
|
EngineeringProjectActivitySummary:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- projectMemberCount
|
|
- phaseCount
|
|
- siteCount
|
|
- openTaskCount
|
|
- designCount
|
|
- designsUnderReviewCount
|
|
- inspectionCount
|
|
- upcomingInspectionCount
|
|
- documentCount
|
|
- lastActivityAt
|
|
properties:
|
|
projectMemberCount:
|
|
type: integer
|
|
minimum: 0
|
|
description: Active participation rows; the separate project-manager pointer is not double-counted.
|
|
phaseCount:
|
|
type: integer
|
|
minimum: 0
|
|
siteCount:
|
|
type: integer
|
|
minimum: 0
|
|
openTaskCount:
|
|
type: integer
|
|
minimum: 0
|
|
description: Tasks in todo or in-progress status.
|
|
designCount:
|
|
type: integer
|
|
minimum: 0
|
|
designsUnderReviewCount:
|
|
type: integer
|
|
minimum: 0
|
|
inspectionCount:
|
|
type: integer
|
|
minimum: 0
|
|
upcomingInspectionCount:
|
|
type: integer
|
|
minimum: 0
|
|
documentCount:
|
|
type: integer
|
|
minimum: 0
|
|
lastActivityAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
EngineeringProjectDashboard:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [project, client, projectManager, activity]
|
|
properties:
|
|
project:
|
|
$ref: '#/components/schemas/EngineeringProject'
|
|
client:
|
|
$ref: '#/components/schemas/EngineeringClientSummary'
|
|
projectManager:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/UserSummary'
|
|
- type: 'null'
|
|
activity:
|
|
$ref: '#/components/schemas/EngineeringProjectActivitySummary'
|
|
EngineeringProjectDashboardResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/EngineeringProjectDashboard'
|
|
|
|
EngineeringProjectMember:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- id
|
|
- organizationId
|
|
- projectId
|
|
- user
|
|
- projectRole
|
|
- status
|
|
- joinedAt
|
|
- leftAt
|
|
- version
|
|
- createdAt
|
|
- updatedAt
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
organizationId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
projectId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
user:
|
|
$ref: '#/components/schemas/UserSummary'
|
|
projectRole:
|
|
$ref: '#/components/schemas/EngineeringProjectMemberRole'
|
|
status:
|
|
$ref: '#/components/schemas/EngineeringProjectMemberStatus'
|
|
joinedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
leftAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
version:
|
|
type: integer
|
|
minimum: 1
|
|
createdAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
updatedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
allOf:
|
|
- if:
|
|
properties:
|
|
status:
|
|
const: active
|
|
required: [status]
|
|
then:
|
|
properties:
|
|
leftAt:
|
|
type: 'null'
|
|
- if:
|
|
properties:
|
|
status:
|
|
const: left
|
|
required: [status]
|
|
then:
|
|
properties:
|
|
leftAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
CreateEngineeringProjectMemberRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [userId, projectRole]
|
|
properties:
|
|
userId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
projectRole:
|
|
$ref: '#/components/schemas/EngineeringProjectMemberRole'
|
|
UpdateEngineeringProjectMemberRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [projectRole]
|
|
properties:
|
|
projectRole:
|
|
$ref: '#/components/schemas/EngineeringProjectMemberRole'
|
|
EngineeringProjectMemberResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/EngineeringProjectMember'
|
|
EngineeringProjectMemberCollectionResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data, meta]
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/EngineeringProjectMember'
|
|
meta:
|
|
$ref: '#/components/schemas/CollectionMeta'
|
|
|
|
EngineeringTask:
|
|
type: object
|
|
additionalProperties: false
|
|
required:
|
|
- id
|
|
- organizationId
|
|
- projectId
|
|
- title
|
|
- description
|
|
- status
|
|
- priority
|
|
- createdByUserId
|
|
- assignedToUserId
|
|
- dueAt
|
|
- startedAt
|
|
- startedByUserId
|
|
- completedAt
|
|
- completedByUserId
|
|
- cancelledAt
|
|
- cancelledByUserId
|
|
- cancellationReason
|
|
- version
|
|
- createdAt
|
|
- updatedAt
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
organizationId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
projectId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
title:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 300
|
|
description:
|
|
type: [string, 'null']
|
|
maxLength: 10000
|
|
status:
|
|
$ref: '#/components/schemas/EngineeringTaskStatus'
|
|
priority:
|
|
$ref: '#/components/schemas/EngineeringTaskPriority'
|
|
createdByUserId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
assignedToUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
dueAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
startedAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
startedByUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
completedAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
completedByUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
cancelledAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
cancelledByUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
cancellationReason:
|
|
type: [string, 'null']
|
|
maxLength: 500
|
|
version:
|
|
type: integer
|
|
minimum: 1
|
|
createdAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
updatedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
allOf:
|
|
- if:
|
|
properties:
|
|
status:
|
|
const: todo
|
|
required: [status]
|
|
then:
|
|
properties:
|
|
startedAt: {type: 'null'}
|
|
startedByUserId: {type: 'null'}
|
|
completedAt: {type: 'null'}
|
|
completedByUserId: {type: 'null'}
|
|
cancelledAt: {type: 'null'}
|
|
cancelledByUserId: {type: 'null'}
|
|
cancellationReason: {type: 'null'}
|
|
- if:
|
|
properties:
|
|
status:
|
|
const: in_progress
|
|
required: [status]
|
|
then:
|
|
properties:
|
|
startedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
startedByUserId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
completedAt: {type: 'null'}
|
|
completedByUserId: {type: 'null'}
|
|
cancelledAt: {type: 'null'}
|
|
cancelledByUserId: {type: 'null'}
|
|
cancellationReason: {type: 'null'}
|
|
- if:
|
|
properties:
|
|
status:
|
|
const: completed
|
|
required: [status]
|
|
then:
|
|
properties:
|
|
completedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
completedByUserId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
cancelledAt: {type: 'null'}
|
|
cancelledByUserId: {type: 'null'}
|
|
cancellationReason: {type: 'null'}
|
|
- if:
|
|
properties:
|
|
status:
|
|
const: cancelled
|
|
required: [status]
|
|
then:
|
|
properties:
|
|
completedAt: {type: 'null'}
|
|
completedByUserId: {type: 'null'}
|
|
cancelledAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
cancelledByUserId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
description: Terminal and start metadata are controlled exclusively by task commands.
|
|
CreateEngineeringTaskRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [projectId, title]
|
|
properties:
|
|
projectId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
title:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 300
|
|
description:
|
|
type: [string, 'null']
|
|
maxLength: 10000
|
|
priority:
|
|
allOf:
|
|
- $ref: '#/components/schemas/EngineeringTaskPriority'
|
|
default: medium
|
|
assignedToUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
dueAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
UpdateEngineeringTaskRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
minProperties: 1
|
|
properties:
|
|
title:
|
|
type: string
|
|
minLength: 1
|
|
maxLength: 300
|
|
description:
|
|
type: [string, 'null']
|
|
maxLength: 10000
|
|
priority:
|
|
$ref: '#/components/schemas/EngineeringTaskPriority'
|
|
assignedToUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
dueAt:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Timestamp'
|
|
- type: 'null'
|
|
CompleteEngineeringTaskRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
properties:
|
|
completedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
description: A supplied completion time cannot be in the future or precede task creation.
|
|
CancelEngineeringTaskRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
properties:
|
|
reason:
|
|
type: string
|
|
maxLength: 500
|
|
EngineeringTaskResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/EngineeringTask'
|
|
EngineeringTaskCollectionResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data, meta]
|
|
properties:
|
|
data:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/EngineeringTask'
|
|
meta:
|
|
$ref: '#/components/schemas/CollectionMeta'
|
|
|
|
EngineeringTaskBatchItem:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, version]
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
version:
|
|
type: integer
|
|
minimum: 1
|
|
BatchAssignEngineeringTasksRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [tasks, assigneeUserId, mode]
|
|
properties:
|
|
tasks:
|
|
type: array
|
|
minItems: 1
|
|
maxItems: 100
|
|
uniqueItems: true
|
|
items:
|
|
$ref: '#/components/schemas/EngineeringTaskBatchItem'
|
|
assigneeUserId:
|
|
$ref: '#/components/schemas/Uuid'
|
|
mode:
|
|
$ref: '#/components/schemas/BatchExecutionMode'
|
|
description: Duplicate task IDs are rejected even when their supplied versions differ.
|
|
BatchCompleteEngineeringTasksRequest:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [tasks, mode]
|
|
properties:
|
|
tasks:
|
|
type: array
|
|
minItems: 1
|
|
maxItems: 100
|
|
uniqueItems: true
|
|
items:
|
|
$ref: '#/components/schemas/EngineeringTaskBatchItem'
|
|
completedAt:
|
|
$ref: '#/components/schemas/Timestamp'
|
|
mode:
|
|
$ref: '#/components/schemas/BatchExecutionMode'
|
|
description: Duplicate task IDs are rejected; completedAt follows the single-task completion rules.
|
|
EngineeringTaskBatchSuccess:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, version, status, assignedToUserId]
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
version:
|
|
type: integer
|
|
minimum: 1
|
|
status:
|
|
$ref: '#/components/schemas/EngineeringTaskStatus'
|
|
assignedToUserId:
|
|
oneOf:
|
|
- $ref: '#/components/schemas/Uuid'
|
|
- type: 'null'
|
|
EngineeringTaskBatchFailure:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [id, code, message, currentVersion]
|
|
properties:
|
|
id:
|
|
$ref: '#/components/schemas/Uuid'
|
|
code:
|
|
type: string
|
|
pattern: '^[A-Z][A-Z0-9_]+$'
|
|
message:
|
|
type: string
|
|
maxLength: 500
|
|
currentVersion:
|
|
type: [integer, 'null']
|
|
minimum: 1
|
|
EngineeringTaskBatchResult:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [mode, succeeded, failed]
|
|
properties:
|
|
mode:
|
|
$ref: '#/components/schemas/BatchExecutionMode'
|
|
succeeded:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/EngineeringTaskBatchSuccess'
|
|
failed:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/EngineeringTaskBatchFailure'
|
|
EngineeringTaskBatchResponse:
|
|
type: object
|
|
additionalProperties: false
|
|
required: [data]
|
|
properties:
|
|
data:
|
|
$ref: '#/components/schemas/EngineeringTaskBatchResult'
|
|
|
|
security:
|
|
- bearerAuth: []
|