Saltar al contenido principal

Implementation Portal API Surface

Source-backed interface reference for apps/backend/src/implementation-portal/.

Use this page when wiring clients, MCP tools, import jobs, or support scripts. For domain rules and service responsibilities, read Implementation Portal Module first.


Scope

The implementation portal exposes three interface surfaces:

  • team APIs for guide templates, boards, steps, and invoice drafts
  • public/client portal APIs keyed by a board share token
  • data-import handlers for templates, boards, time entries, and assignments

Paths below are controller paths. Prepend the backend prefix used by the running environment.


Interface and auth map

SurfaceCodepathAuth model
Team guide templatesinterfaces/http/guide-template.controller.tsglobal app auth; most routes have @Permission(PolicyResource.GuideTemplate, ...)
Team implementation boardsinterfaces/http/implementation-board.controller.tsglobal app auth plus PolicyResource.ImplementationBoard permissions
Team board stepsinterfaces/http/board-step.controller.tsglobal app auth plus PolicyResource.BoardStep permissions
Team invoice draftsinterfaces/http/invoice-draft.controller.tsglobal app auth plus PolicyResource.InvoiceDraft permissions
Client portal read/OTPinterfaces/http/client-portal.controller.ts@IsPublic() on GET /portal/:shareToken, POST .../request-otp, and POST .../verify-otp
Client portal session check / writesinterfaces/http/client-portal.controller.tsPortalSessionGuard only; routes are not @IsPublic() today
Bulk importapps/backend/src/data-import/*global app auth plus business-access checks in DataImportController

Confirmed portal write auth behavior

Public portal read and OTP routes are marked @IsPublic(). These routes are not:

  • GET /portal/:shareToken/auth/session
  • PATCH /portal/:shareToken/steps/:stepId/complete
  • PATCH /portal/:shareToken/steps/:stepId/client-approve
  • POST /portal/:shareToken/steps/:stepId/attachments
  • POST /portal/:shareToken/steps/:stepId/comments

Those handlers use PortalSessionGuard, but the global Firebase AuthGuard runs first. For an OTP-only client that sends only x-portal-session-token / flowpos-portal-session (no Firebase ID token), AuthGuard throws 401 Unauthorized with message No token provided (apps/backend/src/auth/infrastructure/auth.guard.ts) before PortalSessionGuard can validate the portal session.

Treat OTP-only portal writes and session checks as blocked by current controller metadata, not as an intermittent integration issue. The intended fix is to mark those handlers @IsPublic() so PortalSessionGuard becomes the auth boundary for client portal mutations.


Team API groups

Guide templates

GuideTemplateController owns template metadata, template tree edits, cloning, and guide resources.

MethodPathPurpose
GET/businesses/:businessId/guide-templatesList templates for a business
POST/businesses/:businessId/guide-templatesCreate a guide template
GET/businesses/:businessId/guide-templates/:idGet a template with phases and steps
PUT/businesses/:businessId/guide-templates/:idUpdate template metadata
DELETE/businesses/:businessId/guide-templates/:idDelete a template
POST/businesses/:businessId/guide-templates/:id/cloneClone a template into an independent copy
POST/businesses/:businessId/guide-templates/:id/phasesAdd a phase
PUT/businesses/:businessId/guide-templates/:id/phases/:phaseIdUpdate phase metadata/order
DELETE/businesses/:businessId/guide-templates/:id/phases/:phaseIdDelete a phase and its steps
POST/businesses/:businessId/guide-templates/:id/phases/:phaseId/stepsAdd a step
PUT/businesses/:businessId/guide-templates/:id/phases/:phaseId/steps/:stepIdUpdate a step
DELETE/businesses/:businessId/guide-templates/:id/phases/:phaseId/steps/:stepIdDelete a step

Guide resources attach implementation material to either phases or steps:

MethodPathPurpose
GET/businesses/:businessId/guide-templates/:id/phases/:phaseId/resourcesList phase resources
POST/businesses/:businessId/guide-templates/:id/phases/:phaseId/resourcesAdd a phase resource (link, comment, or file)
GET/businesses/:businessId/guide-templates/:id/phases/:phaseId/steps/:stepId/resourcesList step resources
POST/businesses/:businessId/guide-templates/:id/phases/:phaseId/steps/:stepId/resourcesAdd a step resource
PUT/businesses/:businessId/guide-templates/:id/resources/:resourceIdUpdate resource title, URL, or body
DELETE/businesses/:businessId/guide-templates/:id/resources/:resourceIdDelete a resource

File resources use the storage port and return signed read URLs when listing. That signing behavior is an interface concern; guide resources remain domain entities independent of GCS.

Implementation boards

ImplementationBoardController exposes the team board lifecycle.

MethodPathPurpose
GET/businesses/:businessId/implementation-boardsList boards with health and completion stats
POST/businesses/:businessId/implementation-boardsCreate a board, optionally from a template
GET/businesses/:businessId/implementation-boards/:idGet board tree with stats
PUT/businesses/:businessId/implementation-boards/:idUpdate board metadata
DELETE/businesses/:businessId/implementation-boards/:idArchive a board
PATCH/businesses/:businessId/implementation-boards/:id/statusMove board through valid status transitions
POST/businesses/:businessId/implementation-boards/:id/regenerate-share-tokenRotate the client portal share token

Board status transitions are domain behavior on ImplementationBoard: draft -> active -> completed -> archived, with archive allowed from any non-archived state.

Board steps

BoardStepController exposes team operations for execution, billing evidence, files, and comments.

MethodPathPurpose
PATCH/businesses/:businessId/board-steps/:stepId/statusStart, complete, approve, reject, or return a step
PATCH/businesses/:businessId/board-steps/:stepId/assignAssign a team member
PATCH/businesses/:businessId/board-steps/:stepId/due-dateUpdate due date
GET/businesses/:businessId/board-steps/:stepId/time-entriesList time entries
POST/businesses/:businessId/board-steps/:stepId/time-entriesLog hours on hourly-billed steps
PUT/businesses/:businessId/board-steps/:stepId/time-entries/:entryIdEdit a time entry
DELETE/businesses/:businessId/board-steps/:stepId/time-entries/:entryIdDelete a time entry
GET/businesses/:businessId/board-steps/:stepId/attachmentsList attachments with signed URLs
POST/businesses/:businessId/board-steps/:stepId/attachmentsUpload an attachment
DELETE/businesses/:businessId/board-steps/:stepId/attachments/:attachmentIdDelete an attachment
GET/businesses/:businessId/board-steps/:stepId/commentsList comments, including internal comments
POST/businesses/:businessId/board-steps/:stepId/commentsPost a team comment
PUT/businesses/:businessId/board-steps/:stepId/comments/:commentIdEdit own comment
DELETE/businesses/:businessId/board-steps/:stepId/comments/:commentIdDelete own comment

Important constraints from domain/use-case code:

  • step attachments are capped at 50 MB and must use an allowed MIME type
  • hourly time entries require an hourly step and a minimum of 0.25 hours
  • time-entry edit/delete is blocked once the step is linked to an invoice draft
  • document-step completion requires at least one attachment

Invoice drafts

InvoiceDraftController exposes draft generation and lifecycle actions.

MethodPathPurpose
GET/businesses/:businessId/implementation-boards/:boardId/invoice-draftsList drafts for a board
POST/businesses/:businessId/implementation-boards/:boardId/invoice-draftsGenerate a draft from billable steps
GET/businesses/:businessId/invoice-drafts/:idGet a draft
PATCH/businesses/:businessId/invoice-drafts/:id/line-items/:stepIdUpdate line-item description
POST/businesses/:businessId/invoice-drafts/:id/sendPush to payment adapter and mark sent
POST/businesses/:businessId/invoice-drafts/:id/mark-paidManually mark paid
POST/businesses/:businessId/invoice-drafts/:id/voidVoid a draft or sent invoice

Billing eligibility is application/domain behavior in InvoiceCalculatorService, not controller behavior.


Client portal API

ClientPortalController is intentionally narrower than the team API. It reads a scrubbed board view and allows client actions only after OTP verification.

MethodPathPurpose
GET/portal/:shareTokenPublic client board view without billing-sensitive fields
POST/portal/:shareToken/auth/request-otpRequest an email OTP; always returns 204 to avoid email enumeration
POST/portal/:shareToken/auth/verify-otpVerify OTP and receive a portal session token
GET/portal/:shareToken/auth/sessionValidate the current portal session header or cookie; returns 204 when valid
PATCH/portal/:shareToken/steps/:stepId/completeComplete a client-assigned step
PATCH/portal/:shareToken/steps/:stepId/client-approveClient-approve a completed implementer step
POST/portal/:shareToken/steps/:stepId/attachmentsClient upload for document steps; multipart body must include file and boardId
POST/portal/:shareToken/steps/:stepId/commentsClient comment, always persisted as client_visible

Portal session tokens can be sent in:

  • x-portal-session-token
  • flowpos-portal-session

verify-otp returns the session token in the JSON response and also sets the flowpos-portal-session cookie. In production, the cookie is httpOnly, secure, and sameSite=none; in non-production it uses sameSite=lax.


Data import surface

Implementation Portal imports are registered in DataImportModule, not in the implementation portal module itself. The import handlers still call the implementation portal application services and repositories.

Supported import types:

Import typeRequired template headers
implementation-portal-templatetemplateName, templateDescription, vertical, phaseName, phaseDescription, phaseOrder, stepName, stepDescription, stepOrder, stepType, assigneeRole, billableType, fixedAmount, hourlyRate, dueOffsetDays, isVisibleToClient
implementation-portal-boardclientName, clientEmail, templateName, startDate
implementation-portal-time-entryboardName, stepName, hours, description, loggedAt, externalId
implementation-portal-step-assignmentboardName, stepName, assignedTo, authorizedBy

Use GET /data-import/templates/:importType to download generated templates. Use the normal data-import upload/preview endpoints for execution; the import controller enforces business access from the authenticated Firebase user before running a job.