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.
Endpoints
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)
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)
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 // Objeto do membro da equipe no padrão da aplicação
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 // UUID do item de revisão
groupId: string // ID do grupo/parte
groupName: string // Nome do grupo
partName: string // Nome da parte
htmlContent: string // HTML do contrato com variáveis substituídas
thumbnailUrl?: string // Thumbnail (opcional)
status: 'pending' | 'approved' | 'rejected' | 'changes_requested'
commentsCount: number
approvals: Array<{
reviewerId: string
status: 'pending' | 'approved' | 'rejected'
comment?: string
reviewedAt?: Date
}>
totalRequired: number // Total de revisores obrigatórios
approvedCount: number // Quantos obrigatórios já aprovaram
}>
reviewers: Array<{
id: string
userId: string
name: string
email: string
avatar?: string
isRequired: boolean
order: number
totalContracts: number // Total de contratos para revisar
reviewedCount: number // Quantos já revisou
approvedCount: number
rejectedCount: number
}>
allApproved: boolean // true quando todos os revisores obrigatórios aprovaram todos os contratos
}Regras:
contractssão gerados a partir dospartGroupsdo contrato, ohtmlContenté o template HTML com variáveis substituídas pelos valores atribuídos na etapa de modeloallApprovedétruequando todos os revisores comisRequired: trueaprovaram todos os contratostotalRequiredconta apenas revisores obrigatóriosstatusdo contrato é calculado:approvedquando todos obrigatórios aprovaram,rejectedse algum rejeitou,changes_requestedse há rejeição com pendência,pendingcaso contrário- Deve haver pelo menos 1 revisor, não é permitido remover o último
Erros:
404: contrato não encontrado
11. Adicionar Comentário a um Contrato em Revisão
O reviewer seleciona um trecho de texto no HTML do contrato e adiciona um comentário vinculado a esse trecho. Apenas reviewers do contrato podem comentar.
POST /contracts/:id/review/:contractItemId/commentRequest Body:
typescript
{
content: string // Texto do comentário
selectedText?: string // Trecho de texto selecionado no contrato
startOffset?: number // Offset inicial da seleção (opcional)
endOffset?: number // Offset final da seleção (opcional)
pageNumber?: number // Número da página (opcional, legado)
}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
Apenas o autor do comentário pode editá-lo.
PATCH /contracts/:id/review/:contractItemId/comments/:commentIdRequest Body:
typescript
{
content: string // Novo texto do comentário
}Response: 200 OK, retorna ReviewComment atualizado
12. Listar Comentários de um Contrato em Revisão
GET /contracts/:id/review/:contractItemId/commentsResponse: 200 OK
typescript
Array<{
id: string
reviewerId: string
reviewerName: string
reviewerEmail: string
reviewerAvatar?: string
content: string
selectedText?: string
startOffset?: number
endOffset?: number
pageNumber?: number
createdAt: Date
updatedAt?: Date
}>13. Aprovar Contrato em Revisão
PATCH /contracts/:id/review/:contractItemId/approveRequest Body:
typescript
{
comment?: string // Comentário opcional ao aprovar
}Response: 200 OK
typescript
{
success: true
status: 'approved'
}Regras:
- Registra a aprovação do revisor atual para o contrato específico
- Recalcula
allApprovedglobal - Registra entrada no histórico
14. Rejeitar Contrato em Revisão
PATCH /contracts/:id/review/:contractItemId/rejectRequest Body:
typescript
{
reason: string // Motivo da rejeição (obrigatório)
}Response: 200 OK
typescript
{
success: true
status: 'rejected'
}Regras:
reasoné obrigatório- Notifica os responsáveis sobre a rejeição
Erros:
422:reasonobrigatório
15. Adicionar Revisor
POST /contracts/:id/review/reviewersRequest Body:
typescript
{
userId: string
name: string
email: string
isRequired: boolean
}Response: 201 Created, retorna ReviewerSummary
16. Remover Revisor
DELETE /contracts/:id/review/reviewers/:reviewerIdResponse: 204 No Content
Regras:
- Não é permitido remover o último revisor, deve haver pelo menos 1
Erros:
422: não é possível remover o último revisor
17. Enviar Lembrete de Atenção a Revisor
POST /contracts/:id/review/reviewers/:reviewerId/reminderEnvia notificação (email) ao revisor lembrando da revisão pendente.
Response: 200 OK
typescript
{
success: true
}Signature API
Endpoints para gerenciar assinaturas do contrato. Os grupos de assinatura são derivados do signatureConfig do workflow atrelado ao contrato.
18. Overview de Assinaturas
GET /contracts/:id/signaturesRetorna os grupos de assinatura configurados e o status atual de cada signatário.
Response: 200 OK
typescript
{
groups: Array<{
id: string
name: string
order: number
isPartGroup: boolean
members: Array<{
id: string
userId?: string
name: string
email: string
avatar?: string
order: number
status: 'pending' | 'signed' | 'rejected'
signedAt?: Date
rejectionReason?: string
}>
}>
summary: {
totalSigners: number
signedCount: number
pendingCount: number
rejectedCount: number
allSigned: boolean
}
}Regras:
- Os grupos são inicializados a partir do
signatureConfigdo workflow - O grupo com
isPartGroup: trueé populado automaticamente com as partes do contrato (role = 'provider') - A ordem de assinatura é definida pelo campo
orderde cada grupo allSignedétruequando todos os signatários assinaram
Erros:
404: contrato não encontrado
19. Atualizar Configuração de Assinaturas
PUT /contracts/:id/signaturesAtualiza os grupos de assinatura. Usado para adicionar/remover signatários internos antes de iniciar o fluxo.
Request Body:
typescript
{
groups: Array<{
id: string
name: string
order: number
isPartGroup: boolean
members: Array<{
id: string
userId?: string
name: string
email: string
order: number
}>
}>
}Response: 200 OK, retorna ContractSignaturesResponse
Regras:
- Grupos com
isPartGroup: truenão podem ser removidos - A ordem dos grupos determina a sequência de assinatura (campo
order) - Membros só podem ser adicionados/removidos em grupos com
isPartGroup: false
20. Iniciar Fluxo de Assinaturas
POST /contracts/:id/signatures/startInicia o fluxo de assinaturas notificando os primeiros signatários conforme a ordem configurada.
Response: 200 OK
typescript
{
message: string
notifiedSigners: Array<{
id: string
name: string
email: string
}>
}Regras:
- Dispara notificação (email) para os signatários do grupo com menor
order - Após todos do grupo assinarem, notifica o próximo grupo na ordem
- Registra entrada no histórico
Erros:
422: nenhum signatário configurado
21. Assinar Contrato
POST /contracts/:id/signatures/:signerId/signRegistra a assinatura de um signatário.
Request Body:
typescript
{
signatureData?: string // Base64 da imagem da assinatura (se manual)
signatureType?: 'electronic' | 'digital'
ipAddress?: string // IP do signatário (capturado pelo frontend)
userAgent?: string // User agent do navegador
}Response: 200 OK
typescript
{
id: string
status: 'signed'
signedAt: string // ISO 8601
nextSigners: Array<{
// Próximos signatários notificados
id: string
name: string
email: string
}>
}Regras:
- Valida que é a vez do signatário assinar (conforme ordem)
- Registra timestamp, IP e user agent para auditoria
- Se todos do grupo atual assinaram, notifica o próximo grupo
- Se todos assinaram, atualiza status do contrato para
completed - Registra entrada no histórico
Erros:
422: não é a vez deste signatário404: signatário não encontrado
22. Rejeitar Assinatura
POST /contracts/:id/signatures/:signerId/rejectRegistra a recusa de um signatário em assinar o contrato.
Request Body:
typescript
{
reason: string // Motivo da rejeição (obrigatório)
}Response: 200 OK
typescript
{
id: string
status: 'rejected'
rejectionReason: string
rejectedAt: string // ISO 8601
}Regras:
reasoné obrigatório- Notifica os administradores sobre a rejeição
- O fluxo de assinaturas é pausado até resolução
- Registra entrada no histórico
Erros:
422:reasonobrigatório404: signatário não encontrado
23. Enviar Lembrete ao Signatário
POST /contracts/:id/signatures/:signerId/reminderEnvia notificação (email) ao signatário lembrando da assinatura pendente.
Response: 200 OK
typescript
{
message: string
sentTo: string // Email do destinatário
}Regras:
- Só pode enviar lembrete para signatários com status
pending - Limite de 1 lembrete por hora por signatário (opcional, configurável)
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
}UpdateSignaturesPayload
typescript
type UpdateSignaturesPayload = {
groups: Array<{
id: string
name: string
order: number
isPartGroup: boolean
members: Array<{
id: string
userId?: string
name: string
email: string
order: number
}>
}>
}