openapi: 3.2.0 info: title: Operations Hub Projects.team Requests API version: 0.1.1 description: '' servers: [] tags: - name: projects.team-requests paths: /api/projects/{project_id}/team-requests: post: operationId: projects_api_team_request_create_team_request summary: Create Team Request parameters: - in: path name: project_id schema: title: Project Id type: string required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' description: Create a DRAFT TR with N slots; first slot is the lead. tags: - projects.team-requests requestBody: content: application/json: schema: $ref: '#/components/schemas/TeamRequestCreate' required: true security: - APIKeyAuth: [] - CookieAuth: [] get: operationId: projects_api_team_request_list_team_requests summary: List Team Requests parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: query name: paginate schema: default: true description: Enable pagination (false returns all results) title: Paginate type: boolean required: false description: Enable pagination (false returns all results) - in: query name: page schema: default: 1 description: Page number minimum: 1 title: Page type: integer required: false description: Page number - in: query name: page_size schema: default: 50 description: Number of items per page maximum: 1000 minimum: 1 title: Page Size type: integer required: false description: Number of items per page responses: '200': description: OK content: application/json: schema: items: $ref: '#/components/schemas/TeamRequestResponse' title: Response type: array tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/site-options: get: operationId: projects_api_team_request_get_site_options summary: Get Site Options parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: query name: project_location_id schema: anyOf: - type: integer - type: 'null' title: Project Location Id required: false - in: query name: project_location_ids schema: anyOf: - items: type: integer type: array - type: 'null' title: Project Location Ids required: false responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/SiteOptionsResponse' description: 'Services + cert preview for the project''s commercial-offer scope. Without any site param: every service priced on the latest offer (the FE create modal renders these as the Service dropdown). With sites: narrows services to those ``ProjectLocation`` rows and previews the deduped *union* of required certs across them. ``project_location_ids`` is the multi-select form; ``project_location_id`` is kept for back-compat. Registered before ``/{team_request_id}`` so the literal path segment wins the route match.' tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/set-options: get: operationId: projects_api_team_request_get_set_options summary: Get Set Options parameters: - in: path name: project_id schema: title: Project Id type: string required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/SetOptionsResponse' description: 'Robot sets pickable for a new TR. Registered before ``/{team_request_id}`` so the literal path segment isn''t parsed as an integer id by django-ninja''s route matcher.' tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/offer-pairs: get: operationId: projects_api_team_request_get_team_request_offer_pairs summary: Get Team Request Offer Pairs parameters: - in: path name: project_id schema: title: Project Id type: string required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/OfferPairsResponse' description: '(Location, Service) pairs from the project''s latest commercial offer. Backs the combined "Location · Service" field on the TR create modal, mirroring the Set Request picker. Registered before ``/{team_request_id}`` so the literal path segment wins the route match.' tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}: get: operationId: projects_api_team_request_get_team_request summary: Get Team Request parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] patch: operationId: projects_api_team_request_update_team_request summary: Update Team Request parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' description: Edit notes (any non-terminal status); people_count and certs (DRAFT only). tags: - projects.team-requests requestBody: content: application/json: schema: $ref: '#/components/schemas/TeamRequestUpdate' required: true security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/cert-drift: get: operationId: projects_api_team_request_get_cert_drift summary: Get Cert Drift parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/CertDriftResponse' description: Diff TR's required-cert snapshot vs pre-job's current set. tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/apply-cert-drift: post: operationId: projects_api_team_request_apply_cert_drift summary: Apply Cert Drift parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' description: Re-sync TR snapshot + slot certs from pre-job. DRAFT / SUBMITTED only. tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/resolve-cert-token: post: operationId: projects_api_team_request_resolve_team_request_cert_token summary: Resolve Team Request Cert Token parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/CertDriftResponse' description: 'Resolve a customer-typed cert token from pre-job: map / create / drop. Doc-control gated — same permission as send-to-planner, since both govern what reaches the planner''s queue with finalised requirements.' tags: - projects.team-requests requestBody: content: application/json: schema: $ref: '#/components/schemas/ResolveCertTokenPayload' required: true security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/submit: post: operationId: projects_api_team_request_submit_team_request summary: Submit Team Request parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' description: DRAFT → SUBMITTED. Notifies Document Controllers after commit. tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/send-to-planner: post: operationId: projects_api_team_request_send_team_request_to_planner summary: Send Team Request To Planner parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' description: SUBMITTED → SENT_TO_PLANNER. Notifies Planners + WMS after commit. tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/cancel: post: operationId: projects_api_team_request_cancel_team_request summary: Cancel Team Request parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' description: Any non-terminal → CANCELLED. Soft-deletes active slots. Idempotent. tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/duplicate: post: operationId: projects_api_team_request_duplicate_team_request summary: Duplicate Team Request parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' description: Clone TR + slots into a new DRAFT. Rejects CANCELLED source. tags: - projects.team-requests requestBody: content: application/json: schema: anyOf: - $ref: '#/components/schemas/TeamRequestDuplicate' - type: 'null' required: false security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/notes: post: operationId: projects_api_team_request_create_team_request_note summary: Create Team Request Note parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestEventResponse' description: 'Append a free-form note (with optional @-mentions) to a TR''s timeline. Each mentioned user (excluding the actor) is emailed via the ``builder.team_request.note-mention`` template. Email failures are swallowed per-recipient so one bad address can''t drop the rest of the batch or fail the API call.' tags: - projects.team-requests requestBody: content: application/json: schema: $ref: '#/components/schemas/TeamRequestNoteCreate' required: true security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/events: get: operationId: projects_api_team_request_list_team_request_events summary: List Team Request Events parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true - in: query name: paginate schema: default: true description: Enable pagination (false returns all results) title: Paginate type: boolean required: false description: Enable pagination (false returns all results) - in: query name: page schema: default: 1 description: Page number minimum: 1 title: Page type: integer required: false description: Page number - in: query name: page_size schema: default: 50 description: Number of items per page maximum: 1000 minimum: 1 title: Page Size type: integer required: false description: Number of items per page responses: '200': description: OK content: application/json: schema: items: $ref: '#/components/schemas/TeamRequestEventResponse' title: Response type: array description: 'Merged timeline: status changes + activity rows, newest first. Returns a UNION''d values() queryset; ``@paginate`` does LIMIT/OFFSET + COUNT(*) in Postgres. ``TeamRequestEventResponse`` reshapes each row via its before-validator.' tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/mention-candidates: get: operationId: projects_api_team_request_list_team_request_mention_candidates summary: List Team Request Mention Candidates parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/MentionCandidateListResponse' description: 'Users available for @-mention on a TR note. Returns every active user. The previous narrowing to doc-controllers + planner-notification recipients made the @-picker useless in practice — the only person with both perms in a given org is often the operator themselves, so @-mentions degraded to self-mentions. The FE Mentions component filters by typed prefix; the API is intentionally permissive.' tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/slots/{slot_id}: patch: operationId: projects_api_team_request_slots_update_team_request_slot summary: Update Team Request Slot parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true - in: path name: slot_id schema: title: Slot Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' description: 'Patch a single slot''s certs and/or promote it to lead. - ``required_certificate_ids``: when provided, replaces the slot''s set. - ``is_lead=True``: transfers leadership to this slot (auto-demotes the previous lead). ``is_lead=False`` is rejected — leadership only moves via promotion of another slot. - DRAFT: any user with project access; SUBMITTED: doc-control only.' tags: - projects.team-requests requestBody: content: application/json: schema: $ref: '#/components/schemas/TeamRequestSlotUpdate' required: true security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/slots/{slot_id}/approve: post: operationId: projects_api_team_request_slots_approve_team_request_slot summary: Approve Team Request Slot parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true - in: path name: slot_id schema: title: Slot Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' description: Planner approves the WMS-proposed person on this slot. tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/slots/{slot_id}/verify: post: operationId: projects_api_team_request_slots_verify_team_request_slot summary: Verify Team Request Slot parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true - in: path name: slot_id schema: title: Slot Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' description: Document Controller marks the slot's bound PPRSI as APPROVED. tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/slots/{slot_id}/reject: post: operationId: projects_api_team_request_slots_reject_team_request_slot summary: Reject Team Request Slot parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true - in: path name: slot_id schema: title: Slot Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' description: Document Controller rejects the slot's bound PPRSI. tags: - projects.team-requests requestBody: content: application/json: schema: $ref: '#/components/schemas/SlotRejectPayload' required: true security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/slots/{slot_id}/unreject: post: operationId: projects_api_team_request_slots_unreject_team_request_slot summary: Unreject Team Request Slot parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true - in: path name: slot_id schema: title: Slot Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' description: Document Controller clears a rejection, returning the PPRSI to PLANNED. tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] /api/projects/{project_id}/team-requests/{team_request_id}/slots/{slot_id}/unverify: post: operationId: projects_api_team_request_slots_unverify_team_request_slot summary: Unverify Team Request Slot parameters: - in: path name: project_id schema: title: Project Id type: string required: true - in: path name: team_request_id schema: title: Team Request Id type: integer required: true - in: path name: slot_id schema: title: Slot Id type: integer required: true responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/TeamRequestResponse' description: Document Controller resets the slot's bound PPRSI back to PLANNED. tags: - projects.team-requests security: - APIKeyAuth: [] - CookieAuth: [] components: schemas: TeamRequestLocationRef: additionalProperties: false properties: project_location_id: title: Project Location Id type: integer name: title: Name type: string required: - project_location_id - name title: TeamRequestLocationRef type: object OfferPairsResponse: additionalProperties: false properties: pairs: items: $ref: '#/components/schemas/OfferPair' title: Pairs type: array title: OfferPairsResponse type: object OfferPair: additionalProperties: false properties: project_location_id: title: Project Location Id type: integer location_name: title: Location Name type: string service_id: title: Service Id type: integer service_code: title: Service Code type: string service_description: anyOf: - type: string - type: 'null' title: Service Description required: - project_location_id - location_name - service_id - service_code title: OfferPair type: object SetOptionsResponse: additionalProperties: false properties: sets: items: $ref: '#/components/schemas/RobotSetOptionResponse' title: Sets type: array title: SetOptionsResponse type: object TeamRequestSlotUpdate: additionalProperties: false description: Per-slot PATCH payload. Cert-set replace + lead-transfer. properties: required_certificate_ids: anyOf: - items: type: integer type: array - type: 'null' title: Required Certificate Ids is_lead: anyOf: - type: boolean - type: 'null' title: Is Lead title: TeamRequestSlotUpdate type: object ResolveCertTokenPayload: additionalProperties: false description: 'Body for POST /team-requests/{id}/resolve-cert-token. The doc-controller picks one unresolved token and tells the server what to do with it: ``map`` it to an existing Certificate, ``create`` a fresh Certificate then map, or ``drop`` it (strip with no new requirement). The token is stripped from both pre-job cert answers — the ``required_certifications`` list and the free-text ``other_certification_details`` — across every site the TR spans. Matching nothing is a 422, not a silent success.' properties: matched_text: description: Token to strip from the pre-job cert answers (required_certifications and/or other_certification_details) minLength: 1 title: Matched Text type: string action: description: What to do with the token enum: - map - create - drop title: Action type: string certificate_id: anyOf: - type: integer - type: 'null' description: Existing Certificate FK — required when action='map' title: Certificate Id new_certificate: anyOf: - $ref: '#/components/schemas/CertificateCreate' - type: 'null' description: Full CertificateCreate payload — required when action='create' required: - matched_text - action title: ResolveCertTokenPayload type: object CertificateCreate: additionalProperties: false description: Schema for creating a Certificate. properties: name: description: Certificate name minLength: 1 title: Name type: string code: anyOf: - type: string - type: 'null' description: Unique short code title: Code description: anyOf: - type: string - type: 'null' description: Description title: Description default_validity_months: anyOf: - type: integer - type: 'null' description: Default expiry window in months. Omit / null for non-expiring certificates. title: Default Validity Months is_active: default: true description: Whether the certificate is active title: Is Active type: boolean country_id: anyOf: - type: integer - type: 'null' description: Country the certificate is intrinsic to. Omit for global. title: Country Id category_id: anyOf: - type: integer - type: 'null' description: Category FK title: Category Id source: anyOf: - type: string - type: 'null' description: Origin system (e.g. "bamboohr") title: Source required: - name title: CertificateCreate type: object CertDriftItem: additionalProperties: false description: One cert that's been added or removed in pre-job vs the TR snapshot. properties: id: title: Id type: integer name: title: Name type: string required: - id - name title: CertDriftItem type: object TeamRequestSlotResponse: additionalProperties: false properties: id: title: Id type: integer is_lead: title: Is Lead type: boolean required_certificate_ids: items: type: integer title: Required Certificate Ids type: array wms_resource_id: anyOf: - type: integer - type: 'null' title: Wms Resource Id wms_synced_at: anyOf: - format: date-time type: string - type: 'null' title: Wms Synced At approved_at: anyOf: - format: date-time type: string - type: 'null' title: Approved At rejection_reason: anyOf: - type: string - type: 'null' title: Rejection Reason pprsi_id: anyOf: - type: integer - type: 'null' title: Pprsi Id pprsi_status: anyOf: - type: string - type: 'null' title: Pprsi Status pprsi_resource_id: anyOf: - type: integer - type: 'null' title: Pprsi Resource Id pprsi_resource_name: anyOf: - type: string - type: 'null' title: Pprsi Resource Name required: - id - is_lead title: TeamRequestSlotResponse type: object TeamRequestNoteCreate: additionalProperties: false description: 'POST .../notes body — free-form note with optional @-mentions. ``mentioned_user_ids`` is persisted as a sidecar list on the activity row so a later email-notification step can resolve recipients without re-parsing the note body. Order-preserving; duplicates are dropped.' properties: text: maxLength: 4000 minLength: 1 title: Text type: string mentioned_user_ids: items: type: integer title: Mentioned User Ids type: array required: - text title: TeamRequestNoteCreate type: object TeamRequestServiceRef: additionalProperties: false description: One service this TR's set will perform (offer-scoped). properties: service_id: title: Service Id type: integer service_code: title: Service Code type: string service_description: anyOf: - type: string - type: 'null' title: Service Description required: - service_id - service_code title: TeamRequestServiceRef type: object MentionCandidateListResponse: additionalProperties: false properties: candidates: items: $ref: '#/components/schemas/MentionCandidate' title: Candidates type: array title: MentionCandidateListResponse type: object CertDriftResponse: additionalProperties: false description: 'Diff between TR''s required-cert snapshot and pre-job''s current set. ``can_apply`` reflects the FSM rule that re-syncing certs once the TR has been sent to the planner would silently change the requirements they''re already planning against. The FE renders the banner but hides the Apply button in that case.' properties: added: items: $ref: '#/components/schemas/CertDriftItem' title: Added type: array removed: items: $ref: '#/components/schemas/CertDriftItem' title: Removed type: array unresolved_tokens: items: type: string title: Unresolved Tokens type: array unresolved_text: default: '' description: Raw customer-typed cert blob title: Unresolved Text type: string can_apply: default: true title: Can Apply type: boolean title: CertDriftResponse type: object SiteServiceOption: additionalProperties: false description: One service priced on the project's latest commercial offer. properties: id: title: Id type: integer code: title: Code type: string description: anyOf: - type: string - type: 'null' title: Description required: - id - code title: SiteServiceOption type: object TeamRequestResponse: additionalProperties: false properties: id: title: Id type: integer display_id: title: Display Id type: string project_id: title: Project Id type: integer project_location_id: title: Project Location Id type: integer project_location_ids: description: 'Full site set: primary project_location_id first, then additional sites.' items: type: integer title: Project Location Ids type: array project_locations: description: Full site set with display names, in the same order as project_location_ids (primary first). items: $ref: '#/components/schemas/TeamRequestLocationRef' title: Project Locations type: array service_id: anyOf: - type: integer - type: 'null' description: Primary service (first of ``services``), kept for back-compat. title: Service Id service_code: anyOf: - type: string - type: 'null' title: Service Code service_description: anyOf: - type: string - type: 'null' title: Service Description services: description: Every service this TR's set will perform, primary first. items: $ref: '#/components/schemas/TeamRequestServiceRef' title: Services type: array service_ids: description: Ids of ``services``, in the same order (primary first). items: type: integer title: Service Ids type: array pprs_id: title: Pprs Id type: integer people_count: title: People Count type: integer status: title: Status type: string notes: anyOf: - type: string - type: 'null' title: Notes required_certificate_ids: items: type: integer title: Required Certificate Ids type: array dates_from: anyOf: - format: date type: string - type: 'null' title: Dates From dates_to: anyOf: - format: date type: string - type: 'null' title: Dates To mobilization_date: anyOf: - format: date type: string - type: 'null' description: 'Effective mobilization date: the linked Set Request''s needed_by_date when one exists on this TR''s PPRS, else the TR''s own value set before any Set Request (the TR-first flow).' title: Mobilization Date mobilization_date_editable: default: false description: True when the mobilization date can be set on the TR itself — no Set Request is linked yet and the TR is not cancelled. Once a Set Request exists it owns the date and this is False (read-only). title: Mobilization Date Editable type: boolean robot_set_id: anyOf: - type: integer - type: 'null' title: Robot Set Id robot_set_name: anyOf: - type: string - type: 'null' title: Robot Set Name robot_set_removed: default: false title: Robot Set Removed type: boolean wms_sync_warnings: items: type: string title: Wms Sync Warnings type: array slots: items: $ref: '#/components/schemas/TeamRequestSlotResponse' title: Slots type: array updated_at: format: date-time title: Updated At type: string required: - id - display_id - project_id - project_location_id - pprs_id - people_count - status - updated_at title: TeamRequestResponse type: object TeamRequestUpdate: additionalProperties: false properties: notes: anyOf: - type: string - type: 'null' title: Notes people_count: anyOf: - maximum: 10 minimum: 1 type: integer - type: 'null' title: People Count required_certificate_ids: anyOf: - items: type: integer type: array - type: 'null' title: Required Certificate Ids mobilization_date: anyOf: - format: date type: string - type: 'null' description: Set the TR-owned mobilization date (TR-first flow). Rejected when a Set Request is already linked — the SR owns the date then. title: Mobilization Date project_location_ids: anyOf: - items: type: integer type: array - type: 'null' description: Full site set for the TR (DRAFT only). The primary project_location (tied to the PPRS) is always kept; any other ids become the additional sites. Editing re-derives the required certificates from the new site set. title: Project Location Ids service_id: anyOf: - type: integer - type: 'null' description: Legacy single-service param (DRAFT only). Merged into ``service_ids`` when that is not supplied. Prefer ``service_ids``. title: Service Id service_ids: anyOf: - items: type: integer type: array - type: 'null' description: 'Full service set for the TR (DRAFT only). Replaces the current services wholesale: each id must be priced on the project''s latest offer; an empty list clears them. The first id becomes the primary ``service``.' title: Service Ids title: TeamRequestUpdate type: object SiteOptionsResponse: additionalProperties: false description: 'Services + cert preview for a project''s commercial-offer scope. Services are sourced from ``ProjectOfferItem`` rows on the project''s latest ``ProjectOffer``. With ``project_location_id`` the endpoint also returns the pre-job cert resolution for that location: resolved ``certificates`` (matched to a Certificate row) and ``unresolved_certification_tokens`` (names that didn''t match + free text from ``other_certification_details``). The FE renders unresolved tokens as a warning + deep-link to pre-job; the doc-control approval edge is blocked while any remain.' properties: services: items: $ref: '#/components/schemas/SiteServiceOption' title: Services type: array certificates: items: $ref: '#/components/schemas/CertificateInfo' title: Certificates type: array unresolved_certification_tokens: items: type: string title: Unresolved Certification Tokens type: array title: SiteOptionsResponse type: object CertificateInfo: additionalProperties: false description: A pre-job-resolved cert (a ``Certificate`` row matched by name). properties: id: title: Id type: integer name: title: Name type: string required: - id - name title: CertificateInfo type: object MentionCandidate: additionalProperties: false description: One user available for @-mention in a Set Request note. properties: id: title: Id type: integer name: title: Name type: string email: anyOf: - type: string - type: 'null' title: Email required: - id - name title: MentionCandidate type: object TeamRequestCreate: additionalProperties: false properties: pprs_id: description: PPRS the TR is bound to. title: Pprs Id type: integer people_count: maximum: 10 minimum: 1 title: People Count type: integer notes: anyOf: - type: string - type: 'null' title: Notes required_certificate_ids: anyOf: - items: type: integer type: array - type: 'null' title: Required Certificate Ids service_id: anyOf: - type: integer - type: 'null' description: Legacy single-service param, kept for back-compat. Merged into ``service_ids`` when that is not supplied. Prefer ``service_ids``. title: Service Id service_ids: anyOf: - items: type: integer type: array - type: 'null' description: Commercial-offer-scoped services this TR's set will perform (multi-select). FE picks from /site-options (all services in the project's commercial offer). Empty / omitted means no service binding yet. Server validates each id is in scope. The first id is kept as the primary ``service`` for single-service consumers. title: Service Ids project_location_id: anyOf: - type: integer - type: 'null' description: Fallback site to use when the PPRS row has a NULL project_location (Wrike sync couldn't match the windpark). Server validates the PL is on the same project, then backfills the PPRS so subsequent TR/SR against it auto-resolve. Ignored when the PPRS already has a site. title: Project Location Id project_location_ids: anyOf: - items: type: integer type: array - type: 'null' description: Sites this TR spans (multi-select). The first id is the fallback for a PPRS with no site (same semantics as project_location_id); the resolved primary plus every other id form the TR's site set. Required certificates are the deduped union across all of them. When omitted the TR is single-site (project_location_id / PPRS). title: Project Location Ids required: - pprs_id - people_count title: TeamRequestCreate type: object TeamRequestDuplicate: additionalProperties: false properties: pprs_id: anyOf: - type: integer - type: 'null' description: Optional override; defaults to the source TR's PPRS. title: Pprs Id project_location_id: anyOf: - type: integer - type: 'null' description: Fallback site when the target PPRS has no project_location. Same semantics as on TeamRequestCreate. title: Project Location Id project_location_ids: anyOf: - items: type: integer type: array - type: 'null' description: Override the cloned site set. When given, the first id is the PPRS fallback and the full list becomes the duplicate's sites; when omitted, the source TR's site set is cloned. title: Project Location Ids service_id: anyOf: - type: integer - type: 'null' description: Legacy single-service override. Merged into ``service_ids`` when that is not supplied. Prefer ``service_ids``. title: Service Id service_ids: anyOf: - items: type: integer type: array - type: 'null' description: Override the cloned services (each must be on the project's latest offer). When omitted, the source TR's full service set is cloned. title: Service Ids title: TeamRequestDuplicate type: object RobotSetOptionResponse: additionalProperties: false properties: id: title: Id type: integer name: title: Name type: string pprs_id: title: Pprs Id type: integer project_location_id: anyOf: - type: integer - type: 'null' title: Project Location Id dates_from: anyOf: - format: date type: string - type: 'null' title: Dates From dates_to: anyOf: - format: date type: string - type: 'null' title: Dates To required: - id - name - pprs_id title: RobotSetOptionResponse type: object TeamRequestEventResponse: additionalProperties: false description: 'One row in the unified TR timeline. Carries the union of status-change and activity fields; the ``event_type`` discriminator tells the frontend how to render it (``STATUS_CHANGE`` vs an activity type like ``NOTE_ADDED``). ``id`` is unique within the response across both backing tables — activity ids are offset by 1e9 so the frontend can use it as a stable React key.' properties: id: title: Id type: integer event_type: title: Event Type type: string description: title: Description type: string resource_id: anyOf: - type: integer - type: 'null' title: Resource Id resource_name: anyOf: - type: string - type: 'null' title: Resource Name old_status: anyOf: - type: string - type: 'null' title: Old Status new_status: anyOf: - type: string - type: 'null' title: New Status created_at: format: date-time title: Created At type: string created_by_name: anyOf: - type: string - type: 'null' title: Created By Name created_by_email: anyOf: - type: string - type: 'null' title: Created By Email mentioned_user_ids: items: type: integer title: Mentioned User Ids type: array required: - id - event_type - description - created_at title: TeamRequestEventResponse type: object SlotRejectPayload: additionalProperties: false description: POST .../slots/{id}/reject body. Reason is persisted on the slot. properties: reason: maxLength: 500 minLength: 1 title: Reason type: string required: - reason title: SlotRejectPayload type: object securitySchemes: APIKeyAuth: type: http scheme: bearer CookieAuth: type: apiKey in: cookie name: opshub_prod_sessionid AuthBearer: type: http scheme: bearer