Skip to main content

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.