Skip to content

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 /contracts

Lista 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/board

Retorna 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 /contracts

Cria 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/:id

Atualiza 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/detail

Retorna 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/files

Upload 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á enviando

Response: 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/:fileId

Response: 204 No Content


6. Completar Step (já existe)

PATCH /contracts/:id/steps/:stepKey/complete

Marca 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/status

Endpoint 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: rascunho
  • IN_REVIEW: em revisão (enviado para revisores)
  • PUBLISHED: publicado (enviado para partes, upload ou assinatura)
  • ARCHIVED: arquivado
  • DONE: finalizado

Transição: draftin_review

O backend deve:

  1. Validar que o contrato está em status draft
  2. Alterar o status para in_review
  3. Disparar e-mail para todos os revisores configurados em revisionConfig.reviewers com:
    • Link do contrato para revisão: {APP_URL}/contracts/{id}/review
    • Nome do contrato
    • Nome de quem enviou
    • Prazo (se configurado no workflow)
  4. Registrar entrada no histórico

Erros específicos:

  • 422: Nenhum revisor configurado em revisionConfig

Transição: draft ou in_reviewpublished

O backend deve:

  1. Validar que o contrato está em status draft ou in_review
  2. Alterar o status para published
  3. 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)

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)
  1. Registrar entrada no histórico

Erros específicos:

  • 422: Dados obrigatórios do step atual não preenchidos

Resumo: Transições de Status

Status anteriorStatus enviadoRotina do backendNotificação
draftin_reviewValidar revisores, avançar fluxoE-mail para revisores com link de revisão
draft / in_reviewpublishedValidar dados, avançar fluxoE-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

  1. O contrato é criado em status draft e só muda de status via ações explícitas
  2. O progress é calculado no frontend (média dos steps) e enviado ao backend
  3. O step de Upload é auto-gerenciado: aparece quando há documentos com isDownload: false
  4. Upload de arquivos é feito via endpoint separado (multipart), não faz parte do save geral
  5. Todos os IDs devem ser UUIDs no formato xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
  6. O backend deve aceitar saves parciais, nem todos os campos precisam estar preenchidos
  7. O frontend faz mapeamento dos steps da API (label/completed) para o formato interno (name/status/progress/isCurrent/isEnabled)
  8. O frontend faz mapeamento das parts da API (flat com type/name/email) para o formato interno (person: Person + role)
  9. O papel de cada parte (role) é persistido em ContractPart.role (BORROWER | PROVIDER | NEUTRAL) e decorre da perspectiva herdada do workflow (Workflow.companyRole), não é recalculado na leitura
  10. Em contratos com a empresa como prestadora (companyRole = 'provider'), a contraparte é BORROWER (cliente/tomador) e é listada pelo módulo Clientes (GET /clients, espelho de GET /team)

8. Overview de Uploads por Prestador

GET /contracts/:id/uploads

Retorna 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 é true quando todos os documentos de todos os providers estão com status approved
  • total = quantidade de documentos com isDownload: false no contrato

Erros:

  • 404: contrato não encontrado

9. Revisar Documento Enviado

PATCH /contracts/:id/documents/:docId/review

Aprova 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 campo reason é obrigatório
  • Ao rejeitar, o backend deve notificar o prestador para reenviar o documento
  • Ao aprovar, registrar entrada no histórico

Erros:

  • 422: reason obrigatório quando status = 'rejected'
  • 404: documento não encontrado

10. Overview de Revisão

GET /contracts/:id/review

Retorna 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:

  • contracts são gerados a partir dos partGroups do contrato
  • allApproved é true quando todos os revisores com isRequired: true aprovaram todos os contratos
  • totalRequired conta 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/comment

Request 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/:commentId

Request 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/comments

Response: 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/send

Envia 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 upsert dos OnboardingFormField (idempotente por templateVariableId).
  • 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_SENT no histórico.
  • Cliques subsequentes funcionam como reminder, o template do e-mail (provider-onboarding-request.hbs) tem seções condicionais por hasFormFields e hasDocuments.

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/start

Disparo 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 retornam 400.
  • Faz upsert único dos OnboardingFormField (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_STARTED no 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 status provider, sem prestadores, sem formulário/documentos, ou algum prestador sem owner com e-mail.