Appearance
Contract API - Especificação de Endpoints
Visão Geral
API RESTful para persistência do contrato durante o fluxo de criação. O contrato é salvo como snapshot completo (todos os dados dos steps) através de um botão manual na toolbar.
Perspectiva e papel das partes
Cada contrato tem uma perspectiva: a empresa logada pode ser a tomadora ou a prestadora daquele contrato. A perspectiva é definida no workflow (Workflow.companyRole = 'borrower' | 'provider', configurada no step de Partes do workflow builder) e o contrato herda a perspectiva do workflow referenciado por workflowId.
Cada parte tem papel persistido na coluna ContractPart.role (enum PartRole): BORROWER, PROVIDER ou NEUTRAL. O papel decorre da perspectiva:
companyRole = 'borrower'→ a empresa logada éBORROWER; a contraparte (PJ ou PF) éPROVIDER.companyRole = 'provider'→ a empresa logada éPROVIDER; a contraparte (PJ ou PF) éBORROWER(o cliente/tomador).
Assim, cada item de parts[] no payload de save/detail carrega seu role. A invariante companyId XOR userId permanece. Owner (Contract.companyId) e pagador da assinatura são sempre a empresa logada, independentemente da perspectiva.
Endpoints
Listagem paginada
GET /contractsLista contratos com paginação e filtros (search, status, progress, ref).
status: um ou mais status (draft, active, etc.), repetidos na query (status=draft&status=active) ou separados por vírgula (status=draft,active). Omitido = todos os status.
progress: valores 20, 40, 60, 80 retornam contratos com progresso até esse percentual (<=); 100 retorna contratos acima de 80% (> 80).
Response: envelope paginado com data: Contract[] e meta.
Board kanban (sem paginação)
GET /contracts/boardRetorna todos os contratos da empresa em colunas do kanban — uma coluna por status, na mesma ordem do filtro de status da listagem. O frontend apenas renderiza as colunas recebidas.
Query params: mesmos filtros da listagem (search, status, progress, ref), sem page nem pageSize.
Response: 200 OK
typescript
{
columns: Array<{
key: ContractStatus
contracts: Contract[]
}>
}Ordem das colunas (key): draft, parts, model, documents, in_review, provider, provider_filling, signature, signing, completed, active, finished, overdue, cancelled, archived.
Cada coluna contém apenas contratos com o status correspondente.
1. Criar Contrato (Draft)
POST /contractsCria um novo contrato em status draft com todos os dados atuais dos steps.
Request Body:
typescript
{
name: string // Nome do contrato (obrigatório)
workflowId: string // ID do workflow selecionado
currentStep: {
key: StepKey // Key do step atual
index: number // Índice do step atual
}
steps: ContractStep[] // Snapshot dos steps com status/progress
parts: ContractPart[] // Partes adicionadas (sem id no POST); cada parte carrega role: 'BORROWER' | 'PROVIDER' | 'NEUTRAL'
selectedTemplate?: Template // Template selecionado (objeto completo)
modelConfig?: {
templateId: string
partGroups: PartGroup[]
variableAssignments: VariableAssignment[]
}
variableValues?: VariableValue[]
partGroups?: PartGroup[] // Grupos de partes configurados
documents: DocumentRequirement[] // Documentos configurados
revisionConfig?: {
reviewers: ReviewerMember[]
}
signatureConfig?: {
groups: SignatureGroup[]
}
}Response: 201 Created
typescript
{
id: string // UUID gerado pelo backend
name: string
status: 'draft'
progress: number
createdAt: Date
updatedAt: Date
}2. Atualizar Contrato
PUT /contracts/:idAtualiza um contrato existente com o snapshot completo dos dados atuais.
Request Body: Mesmo schema do POST. O id não é enviado no body, vai na URL.
Response: 200 OK
typescript
{
id: string
name: string
status: ContractStatus
progress: number
updatedAt: Date
}3. Carregar Contrato Completo (para edição)
GET /contracts/:id/detailRetorna o contrato com todos os dados necessários para restaurar o estado no frontend.
Response: 200 OK
typescript
{
id: string
name: string
status: ContractStatus
progress: number
workflowId: string
currentStep: {
key: StepKey
index: number
}
steps: ContractStep[]
parts: ContractPart[]
selectedTemplate?: Template
modelConfig?: ModelStepConfig
variableValues?: VariableValue[]
partGroups?: PartGroup[]
documents: DocumentRequirement[]
revisionConfig?: RevisionStepConfig
signatureConfig?: SignatureStepConfig
uploadedFiles: UploadedFile[] // Arquivos já enviados no step Upload
createdAt: Date
updatedAt: Date
}4. Upload de Arquivo (Step Upload)
POST /contracts/:id/documents/:docId/filesUpload de arquivo para um documento específico. Usa multipart/form-data.
Request:
Content-Type: multipart/form-data
file: File // Arquivo binário
partId: string // ID da parte que está enviandoResponse: 201 Created
typescript
{
id: string // UUID do arquivo
documentId: string
partId: string
fileName: string
fileUrl: string
fileSize: number
uploadedAt: Date
}5. Remover Arquivo Enviado
DELETE /contracts/:id/documents/:docId/files/:fileIdResponse: 204 No Content
6. Completar Step (já existe)
PATCH /contracts/:id/steps/:stepKey/completeMarca um step como completo e avança o fluxo.
Response: 200 OK
typescript
{
success: true
stepKey: string
}7. Transição de Status
PATCH /contracts/:id/statusEndpoint dedicado para transição de status do contrato. Recebe apenas o novo status.
Request Body:
typescript
{
status: string // Valores aceitos (uppercase): DRAFT, IN_REVIEW, PUBLISHED, ARCHIVED, DONE
}Response: 200 OK
typescript
{
id: string
status: string
}Importante: O frontend armazena status em lowercase (
draft,in_review,published, etc.). O service converte para uppercase antes de enviar para a API. O backend deve aceitar os seguintes valores:
DRAFT: rascunhoIN_REVIEW: em revisão (enviado para revisores)PUBLISHED: publicado (enviado para partes, upload ou assinatura)ARCHIVED: arquivadoDONE: finalizado
Transição: draft → in_review
O backend deve:
- Validar que o contrato está em status
draft - Alterar o status para
in_review - Disparar e-mail para todos os revisores configurados em
revisionConfig.reviewerscom:- Link do contrato para revisão:
{APP_URL}/contracts/{id}/review - Nome do contrato
- Nome de quem enviou
- Prazo (se configurado no workflow)
- Link do contrato para revisão:
- Registrar entrada no histórico
Erros específicos:
422: Nenhum revisor configurado emrevisionConfig
Transição: draft ou in_review → published
O backend deve:
- Validar que o contrato está em status
draftouin_review - Alterar o status para
published - Identificar o próximo step pendente e executar a rotina:
Se o step atual for upload:
- Disparar e-mail/notificação para todas as partes (
parts) do contrato com:- Link da página de preenchimento:
{APP_URL}/contracts/{id}/fill - Nome do contrato
- Lista de documentos pendentes de upload
- Prazo (se configurado no workflow)
- Link da página de preenchimento:
Se o step atual for signature:
- Disparar e-mail/notificação para todas as partes (
parts) do contrato com:- Link da página de assinatura:
{APP_URL}/contracts/{id}/sign - Nome do contrato
- Instruções de assinatura
- Prazo (se configurado no workflow)
- Link da página de assinatura:
- Registrar entrada no histórico
Erros específicos:
422: Dados obrigatórios do step atual não preenchidos
Resumo: Transições de Status
| Status anterior | Status enviado | Rotina do backend | Notificação |
|---|---|---|---|
draft | in_review | Validar revisores, avançar fluxo | E-mail para revisores com link de revisão |
draft / in_review | published | Validar dados, avançar fluxo | E-mail para partes com link de upload ou assinatura |
Tipos de Referência
SaveContractPayload
typescript
type SaveContractPayload = {
name: string
workflowId: string
status?: ContractStatus
currentStep: { key: string; index: number }
steps: ContractStep[]
parts: Omit<ContractPart, 'id'>[]
selectedTemplate?: Template
modelConfig?: ModelStepConfig
variableValues?: VariableValue[]
partGroups?: PartGroup[]
documents: DocumentRequirement[]
revisionConfig?: RevisionStepConfig
signatureConfig?: SignatureStepConfig
}ContractDetail
typescript
type ContractDetail = {
id: string
name: string
status: ContractStatus
progress: number
workflowId: string
currentStep: { key: string; index: number }
steps: ContractStep[]
parts: ContractPart[]
selectedTemplate?: Template
modelConfig?: ModelStepConfig
variableValues?: VariableValue[]
partGroups?: PartGroup[]
documents: DocumentRequirement[]
revisionConfig?: RevisionStepConfig
signatureConfig?: SignatureStepConfig
uploadedFiles: UploadedFile[]
createdAt: Date
updatedAt: Date
}VariableValue
typescript
type VariableValue = {
id: string
name: string
type: string
source: string
required: boolean
value: string
additional?: string
partId?: string
groupId?: string
}UploadedFile
typescript
type UploadedFile = {
id: string
documentId: string
partId: string
fileName: string
fileUrl: string
fileSize: number
uploadedAt: Date
}Regras de Negócio
- O contrato é criado em status
drafte só muda de status via ações explícitas - O
progressé calculado no frontend (média dos steps) e enviado ao backend - O step de Upload é auto-gerenciado: aparece quando há documentos com
isDownload: false - Upload de arquivos é feito via endpoint separado (multipart), não faz parte do save geral
- Todos os IDs devem ser UUIDs no formato
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx - O backend deve aceitar saves parciais, nem todos os campos precisam estar preenchidos
- O frontend faz mapeamento dos steps da API (
label/completed) para o formato interno (name/status/progress/isCurrent/isEnabled) - O frontend faz mapeamento das parts da API (flat com
type/name/email) para o formato interno (person: Person+role) - O papel de cada parte (
role) é persistido emContractPart.role(BORROWER|PROVIDER|NEUTRAL) e decorre da perspectiva herdada do workflow (Workflow.companyRole), não é recalculado na leitura - Em contratos com a empresa como prestadora (
companyRole = 'provider'), a contraparte éBORROWER(cliente/tomador) e é listada pelo módulo Clientes (GET /clients, espelho deGET /team)
8. Overview de Uploads por Prestador
GET /contracts/:id/uploadsRetorna overview de uploads agrupado por prestador (provider). Usado na tela de validação de documentos enviados.
Response: 200 OK
typescript
{
providers: Array<{
total: number
approved: number
rejected: number
provider: TeamMember
documents: Array<{
id: string
documentId: string
name: string
fileName: string
fileUrl: string
fileSize: number
status: 'pending' | 'approved' | 'rejected'
rejectionReason?: string
uploadedAt: Date
}>
}>
allApproved: boolean
}Regras:
- Apenas prestadores (providers) são incluídos, não o borrower
allApprovedétruequando todos os documentos de todos os providers estão com statusapprovedtotal= quantidade de documentos comisDownload: falseno contrato
Erros:
404: contrato não encontrado
9. Revisar Documento Enviado
PATCH /contracts/:id/documents/:docId/reviewAprova ou rejeita um documento enviado por um prestador.
Request Body:
typescript
{
status: 'approved' | 'rejected'
reason?: string // Obrigatório quando status = 'rejected'
}Response: 200 OK. Retorna o documento atualizado:
typescript
{
id: string
documentId: string
name: string
fileName: string
fileUrl: string
fileSize: number
status: 'approved' | 'rejected'
rejectionReason?: string
uploadedAt: Date
}Regras:
- Quando
status = 'rejected', o camporeasoné obrigatório - Ao rejeitar, o backend deve notificar o prestador para reenviar o documento
- Ao aprovar, registrar entrada no histórico
Erros:
422:reasonobrigatório quandostatus = 'rejected'404: documento não encontrado
10. Overview de Revisão
GET /contracts/:id/reviewRetorna overview completo da revisão com contratos gerados (HTML com variáveis substituídas por parte/grupo) e status dos revisores.
Response: 200 OK
typescript
{
contracts: Array<{
id: string
groupId: string
groupName: string
partName: string
htmlContent: string
thumbnailUrl?: string
status: 'pending' | 'approved' | 'rejected' | 'changes_requested'
commentsCount: number
approvals: Array<{
reviewerId: string
status: 'pending' | 'approved' | 'rejected'
comment?: string
reviewedAt?: Date
}>
totalRequired: number
approvedCount: number
}>
reviewers: Array<{
id: string
userId: string
name: string
email: string
avatar?: string
isRequired: boolean
order: number
totalContracts: number
reviewedCount: number
approvedCount: number
rejectedCount: number
}>
allApproved: boolean
}Regras:
contractssão gerados a partir dospartGroupsdo contratoallApprovedétruequando todos os revisores comisRequired: trueaprovaram todos os contratostotalRequiredconta apenas revisores obrigatórios- Deve haver pelo menos 1 revisor
Erros:
404: contrato não encontrado
11. Adicionar Comentário a um Contrato em Revisão
POST /contracts/:id/review/:contractItemId/commentRequest Body:
typescript
{
content: string
selectedText?: string
startOffset?: number
endOffset?: number
pageNumber?: number
}Response: 201 Created
typescript
{
id: string
reviewerId: string
reviewerName: string
reviewerEmail: string
reviewerAvatar?: string
content: string
selectedText?: string
startOffset?: number
endOffset?: number
pageNumber?: number
createdAt: Date
updatedAt?: Date
}11.1. Editar Comentário
PATCH /contracts/:id/review/:contractItemId/comments/:commentIdRequest Body:
typescript
{
content: string
}Response: 200 OK. Retorna ReviewComment atualizado
12. Listar Comentários de um Contrato em Revisão
GET /contracts/:id/review/:contractItemId/commentsResponse: 200 OK. Array de ReviewComment
13-17. Endpoints de Revisão (Aprovar, Rejeitar, Revisores, Lembretes)
Ver documentação completa nos endpoints 13-17 do arquivo original.
Signature API
Endpoints para gerenciar assinaturas do contrato. Ver docs/api/workflow-signatures.md para configuração de grupos no workflow.
18-23. Endpoints de Assinatura
Ver documentação detalhada dos endpoints de assinatura (overview, configuração, iniciar fluxo, assinar, rejeitar, lembretes).
Tipos de Referência (Signature)
ContractSignaturesResponse
typescript
type ContractSignaturesResponse = {
groups: ContractSignatureGroup[]
summary: ContractSignaturesSummary
}ContractSignatureGroup
typescript
type ContractSignatureGroup = {
id: string
name: string
order: number
isPartGroup: boolean
members: ContractSignerMember[]
}ContractSignerMember
typescript
type ContractSignerMember = {
id: string
userId?: string
name: string
email: string
avatar?: string
order: number
status: 'pending' | 'signed' | 'rejected'
signedAt?: Date
rejectionReason?: string
}ContractSignaturesSummary
typescript
type ContractSignaturesSummary = {
totalSigners: number
signedCount: number
pendingCount: number
rejectedCount: number
allSigned: boolean
}Enviar onboarding ao prestador
POST /contracts/:contractId/providers/:providerId/onboarding/sendEnvia ao owner do prestador (via e-mail) o link único /public/onboarding/:token que cobre, na mesma página, formulário (variáveis manualInputBy: 'provider') e upload de documentos (DocumentRequirement.isDownload === false).
Comportamento:
- Se o contrato tem variáveis provider-fill, faz
upsertdosOnboardingFormField(idempotente portemplateVariableId). - Se há documentos a subir, eles aparecem na sidebar do onboarding pelo mesmo token.
- Sempre gera (ou reutiliza) o token de onboarding por
(contractId, providerId). - Registra
PROVIDER_ONBOARDING_SENTno histórico. - Cliques subsequentes funcionam como reminder, o template do e-mail (
provider-onboarding-request.hbs) tem seções condicionais porhasFormFieldsehasDocuments.
Response 200:
typescript
{ success: boolean }Erros:
404: contrato ou prestador não encontrado.400: contrato não tem formulário nem documentos para o prestador, ou prestador sem owner com e-mail cadastrado.
Iniciar onboarding (dispatch a todos os prestadores)
POST /contracts/:contractId/onboarding/startDisparo em massa: faz o equivalente a POST .../providers/:providerId/onboarding/send para todos os prestadores do contrato em uma única chamada e transiciona o status de provider para provider_filling.
Comportamento:
- Valida que o contrato está em status
provider. Outros status retornam400. - Faz
upsertúnico dosOnboardingFormField(se houver formulário) antes de iterar. - Para cada prestador: resolve owner, gera token, envia e-mail e registra
PROVIDER_ONBOARDING_SENT. - Atualiza o status do contrato para
provider_filling(transição direta via repositório, não passa pelo endpoint genérico de update de status). - Registra
PROVIDER_ONBOARDING_STARTEDno histórico do contrato com{ providerCount }.
Response 200:
typescript
{ success: boolean; providerCount: number }Erros:
404: contrato não encontrado.400: contrato não está em statusprovider, sem prestadores, sem formulário/documentos, ou algum prestador sem owner com e-mail.