Contratos como serviço, na sua plataforma

A API /v1 permite que a sua plataforma crie, complete, acompanhe e assine contratos com validade jurídica entre as partes (MP 2.200-2/2001, art. 10 §2º), com seus templates, seu white-label e a IA perguntando só o que você permitir. O que a integração precisa (criar, analisar, negociar, assinar, prazos e auditoria) está na API. Ficam só na tela do app o chat sobre o contrato, a importação de arquivos, o modelo de cláusulas da empresa e as áreas da empresa.

Gera contratos dos seus clientes? Crie seu intermediador: ambiente de teste grátis na hora, produção a partir de R$ 29 por mês.

Quer plugar num assistente (Claude, ChatGPT, Gemini) em vez de escrever código? Guia de integrações, que inclui um OpenAPI enxuto pronto para GPT Actions e function calling.

Autenticação

Toda chamada leva Authorization: Bearer cnsc_sk_…. Cada chave pertence a um tenant, carrega escopos (contracts:read/write, templates:read/write, sign:write, account:read/write e, para os contratos de nível 2 e 3, contracts:operate e contracts:intervene) e tem rate-limit por minuto. O antigo sign:request continua aceito como sinônimo de sign:write. Chaves são exibidas uma única vez; guarde com segurança. A chave pessoal (cnsc_uk_, da página de integrações) só tem contracts:read/write e account:read, e pedir assinatura com ela vira uma proposta que uma pessoa confirma. A chave de empresa (também cnsc_uk_, gerada em Minha conta, Identidades, por quem responde por uma empresa verificada) é a do sistema da empresa: o que ela cria nasce na carteira da empresa, e o pedido de assinatura sai na hora, para as partes do contrato, dentro do plano de quem gerou a chave. Ela para de valer se essa pessoa deixar de responder pela empresa. Pela chave pessoal você alcança o que alcança na tela, inclusive a restrição por área da empresa; a chave da empresa alcança a carteira inteira. O limite por minuto (GET /v1/usage, campo rate_limit) é 120 na chave pessoal e na de empresa, 60, 120 ou 300 conforme o plano do intermediador e até 30 na chave de teste do intermediador.

curl https://api.contratocombinado.com/v1/usage -H "Authorization: Bearer cnsc_sk_SUACHAVE"

Quickstart: do template ao PDF em 3 chamadas

1. Crie um template com cláusulas, {{placeholders}} tipados e regras:

curl -X POST https://api.contratocombinado.com/v1/templates \
  -H "Authorization: Bearer cnsc_sk_SUACHAVE" -H "Content-Type: application/json" \
  -d '{
    "name": "Campanha com modelo",
    "contract_type": "Contrato de campanha publicitária",
    "body": {"clauses": [
      {"number": 1, "title": "Partes", "text": "AGENCIA: {{agencia}}. MODELO: {{modelo}}."},
      {"number": 2, "title": "Cache", "text": "Cache de {{cache}}, pago em até 15 dias."},
      {"number": 3, "title": "Direito de imagem", "text": "Uso limitado à campanha por {{prazo_imagem}}."}
    ]},
    "variables": [
      {"key": "agencia", "required": true}, {"key": "modelo", "required": true},
      {"key": "cache", "required": true}, {"key": "prazo_imagem", "required": false}
    ],
    "min_clauses": ["Direito de imagem"],
    "question_policy": {"ask": ["prazo_imagem"]}
  }'

2. Gere o contrato. Com dados completos, mode=fill responde na hora (201, sem IA):

curl -X POST https://api.contratocombinado.com/v1/contracts \
  -H "Authorization: Bearer cnsc_sk_SUACHAVE" -H "Content-Type: application/json" \
  -H "Idempotency-Key: pedido-123" \
  -d '{
    "template_id": "TEMPLATE_ID", "mode": "fill",
    "variables": {"agencia": "Sua Agência LTDA", "modelo": "Ana Souza", "cache": "R$ 3.000,00", "prazo_imagem": "12 meses"},
    "parties": [
      {"role": "agencia", "name": "Sua Agência LTDA", "kind": "PJ"},
      {"role": "modelo", "name": "Ana Souza", "kind": "PF"}
    ]
  }'

3. Baixe os artefatos:

curl https://api.contratocombinado.com/v1/contracts/CASE_ID/outputs/draft.pdf -H "Authorization: Bearer cnsc_sk_SUACHAVE" -o contrato.pdf
curl https://api.contratocombinado.com/v1/contracts/CASE_ID/outputs/summary.json -H "Authorization: Bearer cnsc_sk_SUACHAVE"

O que cada parte pode negociar. Em cada cláusula do template, negociacao define o modo: fixa (a parte aceita ou recusa, o texto não muda), faixa (a parte escolhe o valor de uma variável entre min e max), catalogo (a parte escolhe uma das opcoes redigidas por você) ou livre. Com "obrigatoria": true, a cláusula não pode ser removida. Se o template declara negociacao em alguma cláusula, as demais ficam fixa, e o contrato precisa ser criado com mode=fill. Uma proposta fora disso é recusada pelo servidor, inclusive a que nasce da sugestão da IA.

O tipo_contrato que o GET devolve é derivado do template, nunca escolhido: essencial (tudo fixa e "analise_por_lado": false), negociado (alguma cláusula negociável ou análise por lado ligada) ou mediado (nível 3). Enquanto houver tópico aberto na mesa do contrato, o pedido de assinatura responde 409. Em qualquer nível, impasse_status diz se o contrato está em impasse, de que tipo ( divergencia: tópico aberto na mesa; sem_resposta: proposta há mais de 3 dias sem decisão) e desde quando, sem o conteúdo. Cada parte traz requirements: currently_due (o que ela deve agora, inclusive marco.<chave> que o papel dela confirma), past_due (o que já venceu), eventually_due (o que ainda vai ser pedido, como a assinatura), blocked_reason e current_deadline.

Marcos. Em body.marcos o template declara o que acontece depois da assinatura: cada marco tem chave, rotulo, tipo (entrega, evento, pagamento ou data), prazo ({"data": "2026-12-01"}, {"dias_da_assinatura": 30} ou {"variavel": "data_entrega"}), quem_confirma (o papel de uma parte, qualquer_parte ou intermediador, este só com nível 2) e, se quiser, aceite_tacito_dias. Variável pode ter tipo (texto, numero, moeda, data, opcao com opcoes, pessoa); fora do tipo, 422 variable_invalid. O GET devolve em marcos o estado de cada um: pendente, confirmado, contestado, vencido ou aceito_tacitamente (passou o prazo e mais esses dias sem contestação). O motivo de uma contestação fica entre as partes.

Prazo de resposta. Proposta de mudança sem resposta não vira aceite: ela expira. body.prazo_de_resposta_dias (de 7 a 30; sem o campo, 15) define o prazo, copiado para o contrato na criação. Três dias antes, quem precisa decidir recebe um lembrete por e-mail. No prazo, a proposta fica expirada e, se era aditivo ou distrato, sai aditivo.recusado ou distrato.recusado com "motivo": "expirada". Pedido de assinatura vencido fica expirada e sai contrato.expirado.

Aditivo fora do modelo. Depois de assinado, o contrato é das partes: nos níveis 0 a 2 um aditivo pode mudar cláusula fixa, sem veto do intermediador. O aditivo.proposto traz fora_do_modelo com o que saiu da trava (fixa:cl_3, removida:cl_5, clausula_nova), e o GET do contrato traz fora_do_modelo (e, a partir do nível 1, clausulas_fora_do_modelo) depois do aceite. No nível 3 o mesmo aditivo espera a sua decisão de exceção, como a contraproposta. Regra do seu negócio (taxa, pagamento pela plataforma) mora nos seus termos de uso com cada parte, não no contrato entre elas.

Troca de signatário. Uma parte que é empresa escolhe quem assina por ela: o responsável pela empresa (ou quem assina hoje) troca para outro membro da empresa com CPF na conta, antes de essa pessoa assinar. As outras partes recebem aviso, sem novo aceite, e você recebe signatario.trocado com o party_id. O intermediador não troca: antes do vínculo, corrija o e-mail pelo PATCH da parte. Pessoa física não troca (seria trocar a parte).

{"number": 2, "title": "Prazo", "text": "Entrega em {{prazo}} dias.",
 "negociacao": {"modo": "faixa", "variavel": "prazo", "min": 10, "max": 30, "unidade": "dias"}}
{"number": 3, "title": "Foro", "text": "Foro de São Paulo.",
 "negociacao": {"modo": "catalogo", "opcoes": [{"id": "rj", "texto": "Foro do Rio de Janeiro."}]}}

Começar com a chave de empresa

Para o sistema da sua empresa enviar documentos para assinar, sem intermediário. Quase tudo abaixo também existe na tela e no conector MCP. O conector não baixa o PDF assinado nem configura o webhook: isso se faz pela API ou pela tela.

  1. Cadastre e verifique a empresa. Em Minha conta, Identidades, informe o CNPJ e envie o contrato social (ou documento equivalente). A decisão chega por e-mail; se recusada, vem com o motivo e você envia outro.
  2. Gere a chave na mesma tela (só quem responde pela empresa). Ela aparece uma vez. Na mesma tela, ligue o webhook para receber os eventos da carteira da empresa.
  3. Envie um PDF para assinar. O arquivo fica intacto; o PDF assinado é ele com o certificado ao fim.
curl -X POST https://api.contratocombinado.com/v1/contracts/documents \
  -H "Authorization: Bearer cnsc_uk_SUACHAVE" \
  -F "file=@contrato.pdf" \
  -F 'meta={"title": "Contrato de prestação de serviços", "send": true,
            "signers": [{"name": "Ana Souza", "email": "ana@cliente.com.br", "cpf": "12345678909"}],
            "authentication": ["cpf"], "expires_in_days": 15}'

Acompanhe. GET /v1/contracts/CASE_ID/signature traz cada pessoa e, quando todos assinam, sealed. O PDF assinado sai em GET /v1/contracts/CASE_ID/signed-pdf (os anexos com ?attachment=N). Com o webhook ligado, você recebe contrato.assinado sem perguntar. Para cancelar o pedido: POST /v1/contracts/CASE_ID/signature-request/cancel; para reenviar o convite a quem não assinou: POST …/signature-request/resend com {"email"}.

O mesmo documento para muitas pessoas: um contrato e um convite para cada uma, em segundo plano.

curl -X POST https://api.contratocombinado.com/v1/contracts/documents/batch \
  -H "Authorization: Bearer cnsc_uk_SUACHAVE" \
  -F "file=@termo.pdf" \
  -F 'meta={"operation_id": "UM-UUID-SEU", "title": "Termo de adesão",
            "signers": [{"name": "Quem assina pela empresa", "email": "rh@suaempresa.com.br"}],
            "recipients": [{"name": "Ana Souza", "email": "ana@x.com"}, {"name": "Bruno Lima", "email": "bruno@x.com"}]}'
curl https://api.contratocombinado.com/v1/contracts/documents/batch/BATCH_ID -H "Authorization: Bearer cnsc_uk_SUACHAVE"

Modelos. Guarde o PDF com quem assina sempre, as posições e as opções em POST /v1/document-templates, e envie só com template_id (sem file), no envio único ou no lote. Com variables, cada contrato sai com o nome, o e-mail, o CPF de quem recebe ou a data escritos no PDF.

Limites

AssinaturasAs do plano de quem gerou a chave, por ciclo. Uso e limite em GET /v1/usage (signatures); acabou, 402 plan_limit.
ConvitesEmpresa verificada: até 60 pedidos e 90 destinatários diferentes a cada 24 horas (conta sem empresa verificada: 20 e 30; conta com menos de 24 horas: 5 e 8). Quem denunciou o seu convite não recebe outro. Cada pessoa recebe no máximo 3 convites seus a cada 24 horas, somando todos os contratos (cancelar e recriar o pedido não zera). Passou, 429 invite_rate, recipient_limit ou reported.
ReenvioAté 3 por pessoa a cada 24 horas (429 resend_limit), com link novo; o anterior para de valer.
ArquivosPDF até 15 MB, com até 9 anexos no mesmo total.
Lote e modelosAté 50 pessoas por lote; até 20 modelos ativos por empresa.

Chave de teste

Gere a chave de teste em Minha conta, Identidades, logo abaixo da chave da empresa. Ela usa as mesmas rotas (documento, lote, modelos, status) e nada sai para fora: os contratos ficam fora da carteira real, não contam no plano, e as pessoas usam e-mail @teste.contratocombinado.com, em que o nome decide o que acontece: aceita@ assina e recusa@ recusa (para várias, aceita+1@, aceita+2@). A assinatura é simulada na hora, sem e-mail, selo nem PDF assinado, então não há pedido para cancelar nem convite para reenviar. O webhook do teste é só dele: ligue com PUT /v1/webhook usando a chave de teste. Criar ou analisar contrato com IA não existe na chave de teste (403 sandbox_unsupported).

Os três modos

fillPuro placeholder: dados completos → render determinístico, síncrono (201), sem IA. Variável obrigatória faltando → 422 com a lista.
assistedO corpo do template, as cláusulas mínimas e a question_policy entram como instrução para a IA, que adapta as cláusulas ao contexto e pergunta o que faltar (202 + job). É orientação ao modelo, não trava automática: confira as cláusulas mínimas na minuta.
fullTexto livre, sem template; a IA conduz a investigação inteira pelo mesmo caminho do assisted (202 + job).

Perguntas pendentes chegam de duas formas: webhook case.needs_input (você renderiza na sua UI e responde via POST /v1/contracts/{id}/answers) ou o embed abaixo.

Embed: o fluxo dentro da sua plataforma

curl -X POST https://api.contratocombinado.com/v1/embed-sessions \
  -H "Authorization: Bearer cnsc_sk_SUACHAVE" -H "Content-Type: application/json" \
  -d '{"contract_id": "CASE_ID", "surface": "complete"}'
# → { "url": "https://contratocombinado.com/#/embed/<token>", "expires_at": ... }

Coloque a url num iframe. Surfaces: complete (o usuário responde as perguntas, em formulário ou conversa), review (cláusulas com tradução leiga) e status. O iframe emite postMessage para a sua página ({source:"cnsc", type}): cnsc:ready, cnsc:status, cnsc:resized (altura) e cnsc:completed, só se a origem da sua página estiver em embed_origins (PUT /v1/branding, até 10 origens https://host[:porta]). Sem lista, nenhuma mensagem sai; o webhook continua sendo a fonte de verdade. A mesma lista decide onde a página aparece: num iframe de origem fora dela, o embed e o sign_url não mostram o contrato, só um link para abrir numa aba própria. O resto do site não abre em iframe nenhum. Ponha referrerpolicy="origin" no seu <iframe>: alguns navegadores só sabem a origem da sua página por aí, e sem ela o iframe recusa. Seu logo (só https, host público), cor (#RRGGBB) e rodapé vão no mesmo PUT /v1/branding.

Assinatura com validade jurídica

curl -X POST https://api.contratocombinado.com/v1/contracts/CASE_ID/signature-requests \
  -H "Authorization: Bearer cnsc_sk_SUACHAVE" -H "Content-Type: application/json" \
  -d '{"signers": [
    {"name": "Sua Agência LTDA", "email": "contratos@suaagencia.com", "role": "agencia"},
    {"name": "Ana Souza", "email": "ana@email.com", "role": "modelo"}
  ]}'
# → { "request_id": ..., "signers": [{ "name", "email", "sign_url" }] }

Cada signatário recebe convite por e-mail (com o seu branding) e você recebe o sign_url de cada um. Embede no seu fluxo (o iframe emite cnsc:signed para as origens de embed_origins). Assinatura eletrônica (MP 2.200-2/2001, art. 10, § 2º): consentimento + código por e-mail + trilha de auditoria (IP, horário) + selagem imutável com manifesto assinado pela plataforma (Ed25519) e âncora pública OpenTimestamps. Acompanhe em GET /v1/contracts/{id}/signature.

Webhooks

Ligue o webhook com PUT /v1/webhook e body {"url": "https://..."} (escopo account:write). O webhook existe para a chave do intermediador, para a chave de empresa e para a chave pessoal do advogado (quem responde por uma identidade no plano de advogado): ele recebe os eventos dos contratos dos clientes que representa e lê esses contratos em GET /v1/contracts/{id} (título, situação, partes e texto; sem escrever). A chave pessoal de quem não é advogado recebe 403. Na primeira vez a resposta traz o secret, uma única vez; trocar só a url mantém o secret, e "rotate": true gera um novo. Na rotação, o secret anterior continua assinando por 24 horas, para você trocar sem perder evento. Eventos: contrato.assinado (o contrato foi selado), case.completed, case.needs_input (com as questions[]), case.blocked e case.failed (o processamento falhou; traz code, estável, como job_failed, ai_unavailable, job_timeout ou job_interrupted, e message, em português). Os case.* trazem, além dos campos de sempre (event, case_id, status...), o mesmo cabeçalho dos demais: id, type, created_at e contract_id. O id é o mesmo em toda retentativa.

Contrato criado com parties também emite parte.convidada, parte.convite_reenviado, parte.entrou, assinatura.parte_assinou, contrato.assinado, contrato.cancelado, contrato.encerrado e, quando uma parte ou você mexe num marco, marco.confirmado, marco.contestado e marco.reaberto, e quando o prazo passa, marco.vencido e marco.aceito_tacitamente (com milestone, a chave do marco), e contrato.pronto_para_assinar quando todas as partes entraram, sem mesa aberta nem proposta pendente (com version_id; qualquer parte pode pedir a assinatura, ou você pela API). Também contrato.criado, contrato.recusado, contrato.expirado (pedido de assinatura vencido), pendencia.vencendo e pendencia.vencida (convite da parte), prazo.proximo (marco em até 3 dias), impasse.aberto/impasse.resolvido, signatario.trocado (a empresa trocou quem assina por ela), rodada.aberta (pedido de ajustes com round_id) e proposta.recebida (com proposta_id e kind; o texto se lê pela API). Aditivo e distrato emitem aditivo.proposto, aditivo.aceito, aditivo.recusado, aditivo.assinado, distrato.proposto, distrato.aceito, distrato.recusado e distrato.assinado; o teste de configuração emite webhook.teste. O corpo traz id (para deduplicar), type, created_at, contract_id, party_id quando houver, a sua metadata e, em data, o contrato exatamente como GET /v1/contracts/{id} devolve em intermediador. Não há garantia de ordem: o GET é a fonte da verdade.

Com a chave de empresa, o webhook (ligado pela API ou em Minha conta, Identidades) recebe os eventos da carteira da empresa, criados pela API ou pela tela: assinatura.parte_assinou, contrato.assinado, contrato.cancelado, contrato.encerrado, os de aditivo e os de distrato, além de rodada.aberta, proposta.recebida, signatario.trocado e contrato.aprovado (aprovação interna completa, com version_id; só nasce em contrato de empresa). Aí data traz title, status, created_at e parties (com name, role, side e signed).

Cada POST segue o padrão Standard Webhooks: webhook-id (o mesmo nas retentativas), webhook-timestamp e webhook-signature, verificáveis pelas bibliotecas oficiais com o secret como ele foi entregue. Continua indo também x-contracts-signature: sha256=<hex>, o HMAC-SHA256 do corpo cru:

import hmac, hashlib

  def verify(raw_body: bytes, header: str, secret: str) -> bool:
      expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
      return hmac.compare_digest(expected, header)

Só resposta 2xx conta como entregue: 3xx (o redirecionamento não é seguido), 4xx e 5xx são falha. Entrega que falha é re-tentada depois de 30 s, 2 min, 10 min, 30 min, 1 h, 3 h, 6 h e 12 h; depois disso fica como falhou. Se o endereço falhar sem parar por 3 dias, o webhook é desativado e os administradores (na chave de empresa, quem responde pela empresa) recebem um e-mail; cadastrar de novo com PUT religa. Para ver o que saiu e o que falhou, use GET /v1/webhook/deliveries?limit=50: cada entrega vem com event, status (entregue, pendente, retentando ou falhou), attempts, last_error, created_at e delivered_at.

Erros e idempotência

Erros seguem problem+json (RFC 7807): {"title", "detail", "status", "code", "param"}. Trate pelo code, que é estável; param aponta o campo que errou, quando se sabe. Exceção: o 422 de validação do corpo ou de parâmetro fora da faixa sai no formato padrão do FastAPI, {"detail": [...]}, sem code. O plano do tenant tem teto mensal (mês UTC) de contratos criados e de pedidos de assinatura: estourou, a resposta é 402 com type: "credito_insuficiente", antes de qualquer trabalho. Uso e limite em GET /v1/usage (campo limits). Contrato criado com parties consome crédito quando é enviado às partes, uma vez por contrato, pelo tipo: essencial, negociado ou mediado. Sem saldo, o send responde 402 antes de qualquer convite sair. O saldo do mês fica em GET /v1/usage (campo credito); quanto um template vai custar, em POST /v1/contracts/estimate. Em POST /v1/contracts, /contracts/analyze, /contracts/documents, /ask, /answers, /attachments e /risk-refresh, envie o header Idempotency-Key: o retry devolve o MESMO resultado e não cobra duas vezes. Em /ask não há o header: a chave vai no corpo, em idempotency_key. No lote (/contracts/batch) a chave vai em cada item (idempotency_key). As demais rotas de escrita não usam a chave: chamar send de novo só reenvia a quem ainda não entrou, e um segundo signature-requests com pedido ativo responde 409 sem criar outro. Em /contracts/documents, o retry com a mesma chave devolve o MESMO contrato (e o mesmo convite), sem criar outro; sem a chave, cada chamada cria um contrato novo. A mesma chave com outro arquivo ou outro meta responde 409 (idempotency_mismatch); enquanto o primeiro pedido ainda roda, o retry recebe 409 idempotency_in_progress com Retry-After. Casos de outro tenant respondem 404 (indistinguível de inexistente).

Catálogo de códigos (code)
  • idempotency_mismatch: a mesma Idempotency-Key com outro pedido (409)
  • idempotency_in_progress: o primeiro pedido com esta Idempotency-Key ainda está rodando (409); repita depois do Retry-After
  • field_too_large: descrição (free_text) acima de 20 mil caracteres, ou context, fields, variables ou usuario_final acima de 20 mil caracteres serializados (422); param diz qual
  • job_failed / ai_unavailable / job_timeout / job_interrupted: o processamento do contrato falhou (campos job.error_code no GET do contrato e code no webhook case.failed): erro geral, IA indisponível, demorou demais, ou foi interrompido; é seguro tentar de novo
  • parties_incomplete / too_many_parties: partes sem documento e e-mail em todas, ou mais de 20
  • party_name_invalid / party_document_invalid / party_email_invalid / party_email_duplicated / party_role_invalid: parte com o campo em param errado
  • party_not_found / party_already_linked / party_invite_missing: sessão acao_da_parte: parte de outro contrato, já vinculada, ou contrato ainda não enviado
  • metadata_invalid: metadata fora do formato
  • template_invalid / template_not_found: template (ou modelo de documento) que não passa na validação, ou que não existe, foi arquivado ou não é da sua parte
  • template_forbidden / templates_limit: modelo de documento por quem não responde pela parte, ou já há 20 ativos
  • cpf_required_by_template / one_recipient_required_by_template: o modelo escreve o CPF de quem recebe e faltou, ou o envio único com variáveis tem mais de uma pessoa recebendo (use o lote)
  • file_empty / file_too_large / file_not_pdf / file_encrypted / file_unreadable / file_duplicate / too_many_files: o PDF (ou um anexo) vazio, acima de 15 MB, não é PDF, tem senha, não abre, repetido, ou mais de 9 anexos
  • signers_invalid / title_invalid / fields_invalid / party_forbidden / documents_limit: documento: signatário sem nome ou e-mail, título, posição fora da página, empresa de que você não faz parte, ou 20 documentos parados sem pedido de assinatura
  • company_key_suspended / company_key_required / company_not_verified / company_key_pf / company_key_missing: chave de empresa: quem a criou deixou de responder pela empresa ou ela perdeu a verificação, rota que exige a chave de empresa, empresa ainda não verificada, parte que é pessoa física, ou empresa sem chave
  • template_not_approved: o intermediador exige aprovação do jurídico e a versão atual do template ainda não foi aprovada (409); aprove no painel
  • subscription_inactive / sandbox_limit: assinatura do plano em atraso ou cancelada suspende a criação de contratos novos (402); o ambiente de teste envia até 200 contratos por mês (429)
  • insufficient_scope: só no envio de documento com send=true sem o escopo sign:write (403). Nas demais rotas, a falta de escopo sai como forbidden
  • contract_not_found: contrato que não existe ou não é da sua chave (404)
  • variable_invalid: valor de variável fora do tipo do template (data, número, opção); param diz qual (422)
  • key_expired: o prazo que você deu à chave acabou (401); gere outra no painel
  • invalid_scopes / invalid_expiry / invalid_label: chave nova pelo painel: permissões fora das da integração ou nenhuma, validade fora de 7, 30, 90 ou 365 dias, rótulo com mais de 60 caracteres (422)
  • plan_limit: assinaturas do plano acabaram neste ciclo (402); uso em GET /v1/usage, campo signatures
  • invite_rate / reported / recipient_limit / resend_limit: teto de convites em 24 horas, a pessoa denunciou o seu convite, a mesma pessoa já recebeu 3 convites seus em 24 horas (somando os pedidos), ou mais de 3 reenvios para ela em 24 horas (429)
  • aguardando_aprovacao / acima_da_alcada / sem_conteudo / fatores_invalidos / observadores_invalidos / cpf_faltando_na_parte: o pedido de assinatura espera aprovação, passa da alçada, o contrato não tem conteúdo, fator indisponível, observadores inválidos, ou falta o CPF de quem assina para o fator pedido
  • no_active_request / signer_not_found / signer_already_answered / signer_waiting_turn / email_unavailable / email_indisponivel: cancelar ou reenviar sem pedido ativo, e-mail fora do pedido, pessoa que já respondeu ou ainda não tem a vez, ou o e-mail não saiu (o link anterior continua valendo)
  • sandbox_unsupported: na chave de teste, criar ou analisar com IA, ou gerar variações de um contrato (grava na carteira real e gasta o plano); use o documento pronto, o lote e os modelos
  • proposals_forbidden: propostas comerciais pela chave de empresa ou de teste: elas são da pessoa, só a chave pessoal as lê e converte (403)
  • upload_invalid / upload_missing / webhook_off: envio direto ao armazenamento com dados errados ou arquivo que não chegou, ou teste de webhook desligado pelo painel. Na /v1, POST /v1/webhook/test com o webhook desligado responde 409 conflict
  • parties_not_linked / negotiation_table_open / proposal_pending / not_ready_to_sign: o que segura a assinatura
  • batch_invalid / batch_item_key_required: lote fora de 1 a 100 itens, ou item sem idempotency_key; no envio em lote de documento, lista, posições ou operation_id fora do formato
  • batch_done: cancelar um lote que já terminou (409)
  • batch_forbidden: envio em lote sem a chave de empresa, ou por quem não responde por empresa verificada (403)
  • credito_insuficiente: sem saldo e sem excedente (402)
  • llm_rate_limited: teto de uso do modelo atingido
  • rate_limited: passou do limite de requisições por minuto da chave (429), ou reenviou um contrato ou convite cedo demais (envio recente, ou reenvio repetido à mesma parte); espere os segundos do cabeçalho Retry-After. O limite está em GET /v1/usage, campo rate_limit
  • production_not_released: conta de produção ainda em revisão (403); use a chave de sandbox
  • cost_ceiling_reached: teto de uso de IA do mês da conta atingido (402); fale com o suporte
  • payment_required: limite de contratos do plano da conta atingido em POST /v1/contracts ou /contracts/analyze (402)
  • payload_too_large: arquivo ou corpo grande demais (413)
  • bad_request / unauthorized / forbidden / not_found / conflict / invalid_request / rate_limited / server_error: os demais, pelo status

Referência de endpoints

POST/v1/templatesCria template (body, variables, min_clauses, question_policy)
GET/v1/templates · /v1/templates/{id}Lista / detalha templates do tenant
POST/v1/templates/{id}/versionsNova versão (snapshot imutável)
POST/v1/contractsCria contrato (mode fill|assisted|full, parties, variables, context)
POST/v1/contracts/batchAté 100 contratos por pedido; cada item com idempotency_key própria e resultado próprio (reenviar não duplica).
POST/v1/contracts/analyzeAnalisa um contrato RECEBIDO (multipart: um PDF/DOCX/TXT/imagem). Abre um caso em modo analyze; mesma função da tool MCP analyze_contract.
GET/v1/contracts · /v1/contracts/{id}Lista (limit e offset) / estado (status, questions, outputs e status do job, que não tem rota própria). Com a chave pessoal, a lista traz a carteira da conta (os criados por qualquer chave ou pela tela, e os que a conta alcança), e não só os criados por essa chave; com a da empresa, a carteira da empresa.
POST/v1/contracts/{id}/answersResponde perguntas pendentes via API. Idempotency-Key evita um segundo turno no retry.
POST/v1/contracts/{id}/askPergunta em linguagem natural sobre o contrato (só leitura, com citações verbatim); mesma função da tool MCP ask_contract. idempotency_key evita cobrar retry.
GET/v1/contracts/{id}/outputs/{file}Baixa draft.json, draft.pdf, summary.json…
POST/v1/contracts/{id}/attachmentsAnexa documento de apoio ao contrato (contexto para a análise). Idempotency-Key evita anexar duas vezes.
POST/v1/contracts/{id}/risk-refreshReavalia o risco na versão mais recente, só nas cláusulas que mudaram; mesma função da tool MCP refresh_risk.
POST/v1/contracts/{id}/batchesPrévia CSV e confirmação de linhas; conta pessoal autorizada, versão salva e operação UUID. Um contrato por sucesso.
GET/v1/contracts/{id}/batchesLotes privados e resultado por linha; selected busca um lote, offset pagina o histórico
GET/v1/proposalsPropostas da conta pessoal: selected, offset e full=true para conteúdo. Escopo contracts:read.
POST/v1/proposals/{uuid}action=save ou convert, operation UUID, revision esperada (0 na criação). Aceite e apresentação exigem confirmação humana na confirmation_path retornada.
GET/v1/contracts/{id}/versionsLista as versões do contrato, na ordem em que nasceram
GET/v1/contracts/{id}/versions/diffO que mudou entre duas versões, cláusula a cláusula
POST/v1/contracts/{id}/sendContrato criado com parties (nome, documento e email): envia a cada parte o convite para vincular a conta por e-mail confirmado e o próprio CPF ou CNPJ. Chamar de novo reenvia só a quem não vinculou (no máximo uma vez por minuto); mesma função da tool MCP send_contract.
PATCH/v1/contracts/{id}/metadataEdita a metadata do contrato (até 30 pares de texto): mescla as chaves enviadas e valor vazio remove. Nunca aparece para as partes; mesma função da tool MCP update_contract_metadata.
GET/v1/contracts/{id}/signed-pdfPDF assinado. Intermediador: contrato de nível de atuação 1 ou mais (definido no template com nivel_atuacao); no nível 0 responde 404. Chave de empresa ou pessoal: o PDF do próprio contrato, e ?attachment=N baixa o anexo N do envelope.
POST/v1/contracts/documentsContrato a partir de um PDF pronto (multipart: file + meta JSON com title, signers e send; attachments: até 9 PDFs assinados junto, tudo até 15 MB). Sem análise: é este arquivo que se assina, intacto, e o PDF assinado sai com o certificado ao fim. Com send=true, chave enterprise e chave de empresa pedem a assinatura na hora; chave pessoal recebe a proposta para confirmar. Mesma função da tool MCP send_document_for_signature.
POST/v1/contracts/documents/batchEnvio em lote, só com a chave de empresa: o mesmo PDF (multipart: file + meta JSON com operation_id, recipients de 1 a 50 e signers fixos até 5) vira um contrato e um convite por destinatário, em segundo plano. Cota do plano e teto de convites valem por linha; uma recusa de cota para o resto. operation_id repetido devolve o mesmo lote. Mesma função da tool MCP send_document_batch.
POST/v1/contracts/documents/batch/{batch_id}/cancelCancela o lote em andamento: quem ainda não recebeu não recebe (not_sent, motivo cancelado); quem já recebeu segue, e cada contrato se cancela pelo pedido de assinatura. Lote concluído: 409 batch_done. Mesma função da tool MCP cancel_document_batch.
GET/v1/contracts/documents/batch/{batch_id}Estado do lote e de cada destinatário (pending, sent, failed, not_sent), com o case_id do contrato e o motivo da falha. Mesma função da tool MCP get_document_batch.
GET/v1/document-templatesModelos de documento: PDFs guardados com a configuração do envio (title, signers que assinam sempre, fields, authentication, signing_order, expires_in_days, observers). O id vai como template_id em POST /v1/contracts/documents e no lote, sem subir o arquivo de novo. Mesma função da tool MCP list_document_templates.
POST/v1/document-templatesCria um modelo (multipart: file + meta JSON com name, title, signers, fields, variables e as opções do pedido). variables marca onde cada envio escreve o nome, o e-mail, o CPF de quem recebe ou a data; o PDF assinado é o preenchido. Até 20 ativos por parte; só quem responde pela parte.
POST/v1/document-templates/{id}/archiveArquiva o modelo: some da lista e libera a vaga. Contratos já criados com ele não mudam.
POST/v1/contracts/{id}/signature-requestsChave enterprise (sign:write): convida os signers do corpo e devolve sign_url por parte. Chave de empresa: convida as partes do contrato por e-mail e devolve request_id, invited e not_sent (sem signers no corpo). Nos dois, authentication (lista com cpf e/ou geo) exige de quem assina, além do código por e-mail, o CPF igual ao cadastrado (signers[].cpf) e a localização do navegador; o que foi conferido vai para a evidência e o manifesto. O fator sms (segundo código pelo celular, signers[].phone) ainda não está disponível e é recusado. signing_order=sequential faz cada signatário receber o convite só depois que o anterior (pela ordem de signers ou das partes) assinou; a mesma posição assina junto. observers (até 10, name e email) acompanham sem assinar: recebem o aviso do envio e a cópia assinada no fim. expires_in_days (1 a 60, padrão 7) define em quantos dias o convite vence; no pedido em ordem, cada vez ganha o prazo inteiro. Chave pessoal usa signature-proposal.
POST/v1/contracts/{id}/archive-proposalChave pessoal: propõe arquivamento e devolve confirmation_url. A pessoa confirma na plataforma; esta chamada não arquiva.
POST/v1/contracts/{id}/signature-proposalChave pessoal: prepara convites de assinatura. Retorna confirmation_url; não envia e-mail até a confirmação na plataforma.
POST/v1/contracts/{id}/share-proposalChave pessoal: propõe acesso de leitura com email, level e days (30, 90 ou 180). Retorna confirmation_url; o link de acesso só aparece para a pessoa que confirmar na plataforma.
POST/v1/contracts/{id}/changes-proposalChave pessoal: recebe uma lista de clause_id, complaint e proposed_text opcional. A pessoa confere destinatários e ajustes na plataforma antes de enviar.
POST/v1/contracts/{id}/actuation-proposalChave pessoal: propõe trocar a atuação (a parte dona: CPF ou empresa) para uma parte do usuário, via nova_parte_id. Retorna confirmation_url; a pessoa confirma na plataforma e a troca só ocorre então. Barra contrato assinado, com convite aberto ou arquivado.
GET/v1/contracts/{id}/historyIntermediador: o histórico do contrato, do mais novo ao mais antigo (criação, convite e entrada das partes, ações da equipe, assinatura, selo, propostas, marcos visíveis, troca de signatário). Cada passo traz at, event, party (o nome, quando o passo é de uma parte) e, conforme o caso, ref (id da proposta) e label (rótulo do marco). Nunca texto digitado, e-mail ou IP. O mesmo histórico aparece no painel.
GET/v1/contracts/{id}/signatureStatus da assinatura: cada parte (com a vez, no pedido em ordem) e a selagem (sealed: quando, hash do PDF e quantos anexos). Vale para a chave de empresa nos contratos da empresa.
GET/v1/contracts/{id}/termsTermos de vida do contrato: vigência, renovação (automatica sim, nao ou nao_diz, e o período), aviso prévio (dias e para que serve: nao_renovar, rescindir ou ambos), reajuste (índice, periodicidade e data-base) e multa rescisória, cada um com o trecho do documento e conferido_em (quando a pessoa conferiu o grupo na plataforma), mais os prazos que a plataforma calcula deles. Só com a chave pessoal ou a da empresa.
GET/v1/agendaObrigações e prazos pendentes dos contratos que a chave alcança (com a chave pessoal, os criados por ela; com a da empresa, a carteira), o mais próximo primeiro. Origem engine: o que o documento diz. Origem prazo: o que a plataforma calcula, como último dia para avisar, próximo reajuste, fim da vigência, fim do sigilo do NDA, devolução do material, os 4 anos do contrato de serviço e o prazo do defeito oculto. Contrato encerrado, arquivado ou cancelado não entra na agenda da API. Paginada com limit e offset. Só com a chave pessoal ou a da empresa.
POST/v1/contracts/{id}/signature-request/cancelChave de empresa: cancela o pedido de assinatura em andamento; os links deixam de valer e o contrato volta a minuta. Mesma função da tool MCP cancel_signature_request.
POST/v1/contracts/{id}/signature-request/resendChave de empresa: reenvia o convite a quem ainda não assinou ({email}), com link novo; o anterior para de valer. Até 3 por pessoa a cada 24 horas; no pedido em ordem, só para quem já tem a vez. Mesma função da tool MCP resend_signature_invite.
POST/v1/embed-sessionsToken de embed (complete | review | status | acao_da_parte). acao_da_parte leva party_id, vale 30 min e deixa a parte confirmar e-mail e documento, ler e assinar dentro do seu produto.
PUT/v1/brandingWhite-label: display_name (até 80), logo_url (https, host público), primary_color (#RRGGBB), footer (até 200) e embed_origins (até 10 origens https que recebem postMessage). Campo inválido: 422.
PUT/v1/webhookLiga/atualiza o webhook de saída do tenant (body {url, rotate?}). O secret sai UMA vez, na primeira vez ou com rotate: true; trocar só a url o mantém. Recusa URL privada/loopback/http (SSRF). Escopo account:write.
GET/v1/webhookEstado do webhook: url configurada, eventos disponíveis, disabled_at (desativado por falha contínua) e até quando o secret anterior ainda assina. Nunca o secret.
GET/v1/webhook/deliveriesÚltimas entregas (limit até 100): event, status, attempts, last_error, created_at, delivered_at. Escopo account:read.
GET/v1/webhook/deliveries/{id}Uma entrega, com o corpo enviado e a próxima tentativa. Escopo account:read.
POST/v1/webhook/deliveries/{id}/redeliverReenvia agora, com o mesmo corpo e o mesmo webhook-id (vale para o que falhou). Escopo account:write.
POST/v1/webhook/testManda um evento webhook.teste ao endereço cadastrado e devolve como foi a entrega. Escopo account:write.
DELETE/v1/webhookDesliga o webhook (limpa url e secret). Escopo account:write.
GET/v1/usageConsumo do tenant por dimensão e, em limits, usado e limite do mês UTC por contrato e assinatura (os mesmos números que barram com 402). Em credito: crédito do mês, consumido, saldo e envios por tipo de contrato. Em rate_limit: o limite de requisições por minuto da chave (passou, 429 com Retry-After).
PATCH/v1/contracts/{id}/parties/{party_id}Nível 2: corrige nome, email, documento ou telefone de parte que ainda não entrou, antes da primeira assinatura. Mudar email ou documento vence o convite dela. Escopo contracts:operate; fica na trilha que as partes leem. Mesma função da tool MCP correct_party.
POST/v1/contracts/{id}/parties/{party_id}/resendNível 2: reenvia o convite a uma parte que ainda não entrou (até 3 por dia por parte). Escopo contracts:operate. Mesma função da tool MCP resend_party_invite.
POST/v1/contracts/{id}/cancelNível 2: cancela antes da primeira assinatura; pedido de assinatura aberto é cancelado junto e sai contrato.cancelado. Escopo contracts:operate. Mesma função da tool MCP cancel_contract.
POST/v1/contracts/{id}/milestones/{chave}/confirmNível 2: confirma um marco que o template dá a você (quem_confirma: intermediador), no contrato assinado. Sai marco.confirmado. Escopo contracts:operate. Mesma função da tool MCP confirm_milestone.
POST/v1/contracts/{id}/mesaNível 3: mensagem do intermediador, com identidade própria, num tópico aberto da mesa ({topico_id, corpo}). Só depois do envio: o convite avisa as partes que ele pode intervir. Escopo contracts:intervene. Mesma função da tool MCP post_to_negotiation_table.
POST/v1/contracts/{id}/exceptions/{proposta_id}/decideNível 3: a proposta de uma parte que sai da trava do template espera você. Aprove ou negue ({decisao: aprovar|negar, motivo?}); aprovada, as partes seguem aceitando; negada, vira recusada. Escopo contracts:intervene. Mesma função da tool MCP decide_exception.
POST/v1/contracts/estimateSem criar nada: o tipo de contrato que o template (template_id) gera e quanto crédito o envio vai consumir, com o saldo de agora. Só chave de tenant (a pessoal recebe 403). Mesma função da tool MCP estimate_contract.
GET/v1/account/capabilitiesO que a empresa da chave pode fazer, segundo o plano dela (com a conta em várias empresas, escolha pelo header X-CNSC-Parte). A chave de intermediador recebe 404.
GET/v1/auditAuditoria da empresa: eventos de assinatura (convite, abertura, assinatura, recusa, cópia enviada) e selagens de todos os contratos no período (from/to em AAAA-MM-DD, até um ano; padrão: últimos 30 dias). Paginado: limit, offset, has_more e next_offset. Nunca IP nem dado do aparelho. Com a chave de empresa, a empresa da chave. Mesma função da tool MCP get_audit_log.

Especificação viva: Swagger · openapi.json

Para testar sem escrever código: importe o endereço do openapi.json no Postman ou no Insomnia (Importar, depois Link). Ele monta a coleção com todas as rotas; ponha a chave em Authorization, tipo Bearer.

Chave sandbox

No sandbox, as partes usam e-mail @teste.contratocombinado.com, e o nome antes do arroba define o que a parte faz: aceita@ entra e assina, recusa@ entra e recusa a assinatura, expira@ nunca entra, documento-divergente@ erra o documento. Para várias partes com o mesmo comportamento, use aceita+1@, aceita+2@. Nenhum e-mail sai, ninguém ganha conta e a assinatura é simulada (sem selo nem PDF assinado). A projeção, os webhooks e o crédito funcionam como em produção.

A chave sandbox (engine real, cota reduzida) sai na hora, quando você cria o seu intermediador; o webhook você mesmo liga com PUT /v1/webhook.