Um flow n8n que lê seu repositório de contratos todo dia útil, calcula a data em que cada acordo com fornecedor precisa ser cancelado ou renegociado — o prazo de aviso prévio, não a data de vencimento —, puxa a utilização de licenças e a variação de preço cotada das suas próprias tabelas, e posta um brief de renovar / renegociar / encerrar para o responsável antes que essa data passe. Custa cerca de US$ 0,05 a US$ 0,07 por brief em inferência do Claude e aproximadamente 22 execuções de n8n por mês.
O workflow vem em apps/web/public/artifacts/contract-renewal-radar-n8n/contract-renewal-radar-n8n.json (15 nodes, um trigger agendado). As três tabelas Postgres que ele precisa estão no arquivo irmão schema.sql, e a configuração de credenciais mais uma sequência de verificação de sete passos estão no _README.md.
Quando usar
Você gerencia mais de 40 acordos com fornecedores, a maioria com renovação automática, e eles vivem em um repositório com datas estruturadas: Ironclad, Docusign Agreement Manager ou equivalente que exponha metadados de vencimento e renovação via API. Alguém é dono do gasto com fornecedores e responde quando um contrato renova a um preço que ninguém aprovou. O ganho não é o alerta; seu CLM já manda alertas. O ganho é que o alerta chega vinculado ao prazo de aviso prévio, traz os números de utilização e variação de preço que o responsável teria que caçar sozinho, e apresenta uma recomendação, de modo que a decisão acontece em uma única mensagem em vez de três semanas de agendamento.
Quando NÃO usar
Pule isso se seus prazos de aviso prévio não estiverem registrados em nenhum lugar legível por máquina. O flow consegue calcular um prazo a partir de uma data de vencimento e um período de aviso prévio; ele não consegue ler a cláusula de aviso dentro de um PDF, e um repositório onde esse campo está vazio na maioria dos registros vai produzir um radar construído quase inteiramente sobre a premissa padrão de 90 dias. Extrair essas cláusulas primeiro é outro projeto e é o que você realmente precisa. Pule se você tem menos de uns 40 acordos, onde uma planilha com quatro lembretes de calendário por contrato ganha com folga. Pule se compras já opera um calendário de renovações que as pessoas seguem: isso substitui um processo ausente, não um que funciona. E pule se suas cláusulas de aviso prévio são escritas em dias úteis e você não estiver disposto a encurtar as faixas de alerta para compensar; o flow conta dias corridos, o que torna o prazo dele posterior ao real.
Setup
Rode o schema.sql primeiro. Ele cria vendor_context (o gasto e uso que seu CLM não guarda), renewal_radar_log (a trilha de auditoria e a chave de deduplicação) e renewal_decisions (escrita por pessoas, e a única tabela que sua métrica de sucesso pode ler com honestidade). Depois importe o JSON, conecte as quatro credenciais placeholder conforme o README e confirme que o timezone do workflow bate com o que rege seus prazos de aviso prévio.
A configuração que realmente importa são algumas constantes no topo de dois nodes Code. Em Normalize + Compute Notice Window, RENEWAL_FIELD_NAMES mapeia os nomes visíveis dos seus campos de registro do Ironclad para a estrutura interna do flow: os ids de campo de metadados de registro do Ironclad são específicos por tenant, então esse é o único ajuste para o qual ninguém consegue entregar um default funcional. DEFAULT_NOTICE_DAYS é 90 e TIERS dispara avisos a 90, 60, 30 e 7 dias antes do prazo. Em Score + Route, as faixas de utilização (0,40 e 0,70), o limite de aumento de preço (10%) e o piso de escalonamento (US$ 50.000 por ano) decidem o que é recomendado e quem fica sabendo.
Espere ajustar esses limites duas vezes. Suba com os defaults, observe um trimestre de decisões de roteamento contra o que seu time de fato decidiu, e mova as faixas para bater.
O que o flow faz
Daily Radar Run — 07:00 Weekdays dispara. Load Radar State lê quais contratos já receberam aviso e em qual faixa. Ironclad — Record Schema resolve os ids de campo do seu tenant contra RENEWAL_FIELD_NAMES, e então Ironclad — List Records percorre o repositório de contratos executados em /public/api/v1/records sob o scope public.records.readRecords. Docusign — List Agreements faz o mesmo contra a Agreement Manager API, cuja extração Iris devolve expiration_date, renewal_type, renewal_notice_date e total_agreement_value como provisions. Merge Contract Sources junta as duas fontes; desativar qualquer um dos nodes é como você opera com um repositório só.
Normalize + Compute Notice Window é onde o trabalho real acontece, e é aritmética deliberadamente sem graça em vez de uma chamada a modelo. Ele mapeia as duas fontes para uma estrutura única e então calcula notice_deadline como vencimento menos o período de aviso prévio, sobre limites de data de calendário em UTC para que o horário de verão não consiga mover uma data legal. Contratos dentro do horizonte de 90 dias recebem uma faixa; todo o resto é descartado. New Tier Only então descarta qualquer contrato já notificado na faixa atual, que é o que impede de postar os mesmos 40 contratos todo dia útil.
Load Spend + Usage puxa licenças contratadas e ativas, e os valores do período anterior e da renovação cotada de vendor_context. Claude — Renewal Brief faz uma chamada à Messages API contra claude-sonnet-5 com thinking adaptativo em esforço médio, devolvendo um brief JSON com uma recomendação, uma justificativa limitada a 60 palavras, até três pontos de negociação e uma lista explícita dos campos que ele precisou e não recebeu. O system prompt proíbe qualquer número que não esteja presente no JSON fornecido, que é o modo de falha que torna perigosos os briefs comerciais redigidos por IA.
Score + Route calcula a recomendação de forma determinística a partir da utilização e da variação de preço, e então compara a resposta do modelo com ela. A aritmética decide a rota; o modelo escreve o texto. A discordância é em si um sinal de roteamento: ela força o contrato para o jurídico com as duas recomendações lado a lado. Needs Legal Escalation divide para #legal-ops-renewals ou #vendor-renewals, e ambos os ramos terminam em Upsert Radar Log, que faz upsert em contract_id para que uma reexecução após falha parcial seja segura.
A realidade do custo
Por brief: uma chamada ao Sonnet 5 com 5.000 a 8.000 tokens de entrada e 800 a 1.500 de saída, mais o thinking adaptativo cobrado como saída, então US$ 0,05 a US$ 0,07 no preço de tabela de US$ 3 por milhão de entrada e US$ 15 por milhão de saída. Um portfólio de 400 contratos costuma empurrar de 30 a 40 contratos para cruzar um limite de faixa por mês, o que dá US$ 2 a US$ 3 de inferência.
Vale entender o lado do n8n porque ele inverte a economia usual por item. O fan-out entre contratos acontece dentro de uma única execução do workflow, então um cron em dias úteis custa cerca de 22 execuções por mês contra as 2.500 incluídas no n8n Cloud Starter a 24 EUR/mês: menos de 1% do plano de entrada, com workflows e usuários ilimitados em todos os planos Cloud desde 2026.
Contra isso, uma única renovação automática não intencional em um contrato de porte médio custa no mínimo quatro dígitos. A economia não é a parte interessante deste flow; a disciplina operacional é.
Métrica de sucesso
Acompanhe a proporção de decisões de renovação registradas em renewal_decisions onde decided_on é igual ou anterior a notice_deadline. A query está no final do schema.sql. Sua linha de base é a fração que hoje é decidida antes da janela fechar, que para a maioria dos times começando isso não é um número que alguém já tenha medido: capture por um trimestre antes de alegar melhoria.
Não acompanhe alertas enviados. Um flow que posta 40 mensagens que ninguém lê tira nota máxima nessa métrica e não mudou nada. O número secundário que vale observar é quantos contratos chegam à faixa de 7 dias ainda sem decisão; se essa contagem não estiver caindo no segundo trimestre, o problema é de responsabilidade, não de ferramenta.
Contra as alternativas
Contra os alertas de renovação nativos do Ironclad e do Docusign: eles disparam de forma confiável e não custam nada a mais, mas notificam em uma data e param aí. Eles não alcançam a utilização de licenças que vive num console de admin do fornecedor nem uma cotação que vive num email, e não produzem uma recomendação, então o responsável ainda precisa montar a decisão sozinho. Essa montagem é o passo que escorrega. Se seu time age de forma consistente sobre o alerta nativo, use o alerta nativo.
Contra uma plataforma de gestão de SaaS — a categoria Zylo, Productiv e Vendr —: elas compram telemetria de uso e rastreio de renovações como produto, o que é genuinamente mais do que este flow entrega no lado do uso. Elas também custam dinheiro de verdade, exigem seu próprio projeto de integração, e ainda assim entregam ao responsável um dashboard em vez de uma decisão. Este flow é a escolha certa quando você já tem os dados de contrato e a lacuna é o último quilômetro.
Contra um script próprio num cron: mesma lógica, e aí você assume os retries, a rotação de credenciais, a paginação e a observabilidade. A razão concreta para construir isso em n8n é a semântica de retry por node na chamada à Anthropic e o log visual de execução: quando a estrutura de um contrato novo quebra o node Normalize, você consegue ver qual registro causou.
Pontos de atenção
Um período de aviso prévio ausente é o único valor que quebra o flow em silêncio. Guarda: Normalize + Compute Notice Window trata um período de aviso nulo ou negativo como DEFAULT_NOTICE_DAYS (90) em vez de zero, e carimba notice_source: 'assumed'. Usar zero como default calcularia o prazo de aviso como a data de vencimento e marcaria o contrato como seguro até o dia exato em que ele renova. Todo card construído sobre um valor presumido diz isso na primeira linha, e contratos presumidos são roteados ao jurídico à força, independente do valor.
Os ids de campo de registro do Ironclad são específicos por tenant, então um mapeamento fixo falha em silêncio na instância de outra pessoa. Guarda: o flow chama o endpoint de schema de registros em tempo de execução e lança schema_field_missing se não conseguir resolver o campo de vencimento ou o de período de aviso. Uma falha barulhenta na importação é o certo aqui; a alternativa é um radar que roda limpo e não acha nada.
A renotificação diária treina as pessoas a silenciar o canal, o que produz exatamente o prazo perdido que o flow existe para evitar. Guarda: o node New Tier Only lê renewal_radar_log.last_tier_notified, então cada contrato posta uma vez por limite de faixa — quatro mensagens em 90 dias, não sessenta.
O modelo consegue produzir um brief fluente sobre um contrato cujos dados de uso nunca carregaram. Guarda: Score + Route calcula a recomendação por aritmética e trata a resposta do modelo como consultiva; quando context_complete é falso, o caminho determinístico devolve renegotiate e nunca renew, porque ausência de dados não é evidência de que os termos atuais estão bons. A discordância entre os dois seta model_agrees: false e força o escalonamento mostrando as duas respostas.
O primo silencioso da fadiga de alertas: um brief que chega sem o nome de ninguém. Guarda: owner_email é carregado do repositório e renderizado no card do canal do responsável. Contratos sem responsável registrado ainda postam, mas caem no canal de escalonamento: uma renovação sem dono é um problema de Legal Ops antes de ser um problema de orçamento.
Stack
n8n orquestra. Claude Sonnet 5 redige o brief pela Messages API da Anthropic. Ironclad e Docusign são os repositórios de contratos; qualquer um deles sozinho é suficiente. Postgres guarda o contexto de gasto e uso, o log do radar e o registro de decisões. Slack recebe os avisos aos responsáveis e os escalonamentos ao jurídico.
Esta é a camada operacional sobre a gestão do ciclo de vida de contratos: o repositório precisa estar populado e os períodos de aviso prévio precisam estar nele antes de qualquer coisa disso rodar. Integrar novos fornecedores por um workflow de due diligence de fornecedores é o que mantém esses campos populados daqui pra frente; este flow é o que cobre os acordos que você assinou antes de alguém estar fazendo isso.
# Contract Renewal Radar — setup
An n8n flow that watches vendor-contract notice windows, pulls spend and usage context, and drafts a renew / renegotiate / terminate brief before the notice deadline — not before the expiration date.
Files in this bundle:
- `contract-renewal-radar-n8n.json` — the workflow export (15 nodes, one schedule trigger)
- `schema.sql` — the three Postgres tables the flow reads and writes
- `_README.md` — this file
## Before you import
Run `schema.sql` against the database you will point the Postgres credential at. The flow reads `vendor_context`, reads and writes `renewal_radar_log`, and never touches `renewal_decisions` — that last table is written by humans and is what your success metric reads.
`vendor_context` can start empty. Contracts with no row still produce a brief; they are flagged `context_complete: false` and routed to the escalation channel rather than being recommended for renewal on missing data.
## Import
n8n → **Workflows** → **Import from File** → select `contract-renewal-radar-n8n.json`. The workflow imports with the trigger **inactive**. Leave it that way until you have run the verification sequence below.
Open **Workflow settings** and confirm **Timezone** reads `America/New_York`, or change it to yours. The notice-deadline math is calendar-date based; a timezone mismatch shifts every deadline by one day, and one day is the whole point of this flow.
## Credentials
Four credentials, each referenced in the export by a `PLACEHOLDER_*` id. n8n will show them as broken until you map each node to a real credential.
### `PLACEHOLDER_POSTGRES_CRED_ID` — Postgres
Standard Postgres credential against the database holding the three tables. Needs `SELECT` on `vendor_context`, and `SELECT`/`INSERT`/`UPDATE` on `renewal_radar_log`. Used by **Load Radar State**, **Load Spend + Usage**, and **Upsert Radar Log**.
### `PLACEHOLDER_IRONCLAD_CRED_ID` — Ironclad
Create a **Header Auth** credential with name `Authorization` and value `Bearer <your token>`. Generate the token in Ironclad under **Company Settings → API**; it needs the OAuth scope `public.records.readRecords`. Used by **Ironclad — Record Schema** and **Ironclad — List Records**.
If your Ironclad instance is on the EU or demo environment, change the host in both nodes — the export points at `ironcladapp.com`.
### `PLACEHOLDER_DOCUSIGN_CRED_ID` — Docusign
An **OAuth2 API** credential against Docusign's Agreement Manager API (formerly Navigator). You will also need `DOCUSIGN_ACCOUNT_ID` set as an environment variable on your n8n instance — the URL interpolates it. The Agreement Manager API reached general availability for eligible Agreement Manager customers in May 2026; if your account predates that, confirm entitlement before wiring this node.
If you run only one contract repository, **disable the node for the one you do not use**. The Merge node is in append mode and passes through whichever branch produces items.
### `PLACEHOLDER_ANTHROPIC_CRED_ID` — Anthropic
An **Anthropic** credential holding your API key. Used by **Claude — Renewal Brief**, which calls `claude-sonnet-5` on the Messages API with adaptive thinking at `medium` effort.
## Configure before first run
Two Code nodes hold the values you will actually tune. Open them and read the constants at the top before you run anything.
**Normalize + Compute Notice Window**
- `RENEWAL_FIELD_NAMES` — the display names of the Ironclad record fields holding expiration date, notice period, renewal type, annual value, and owner email. **These are per-tenant.** The node resolves them against the live schema and throws `schema_field_missing` if it cannot find the expiration or notice-period field. That is deliberate: a silent miss here would mark every contract safe.
- `DEFAULT_NOTICE_DAYS` (90) — what to assume when the paper does not state a notice period. Contracts that hit this default are stamped `notice_source: 'assumed'` and the Slack card says so.
- `TIERS` (90 / 60 / 30 / 7) — how many days before the notice deadline to nudge.
**Score + Route**
- `LOW_UTILIZATION` (0.40) and `SOFT_UTILIZATION` (0.70) — the seat-utilization bands that drive terminate and renegotiate.
- `UPLIFT_RENEGOTIATE` (0.10) — quoted increase above which the flow argues for pushing back.
- `ESCALATION_VALUE_CENTS` (5000000, i.e. $50,000/yr) — above this, contracts go to legal rather than to the business owner.
Also change the two Slack channels (`#legal-ops-renewals` and `#vendor-renewals`) to yours.
## First-run verification
Run these in order with the trigger still inactive. Each step proves one branch works before the next one can hide a failure.
1. **Schema resolution.** Execute **Ironclad — Record Schema** alone. Confirm the response lists your record fields, then execute **Normalize + Compute Notice Window** and check it does not throw `schema_field_missing`. If it does, fix `RENEWAL_FIELD_NAMES` — do not work around it downstream.
2. **Date math.** Temporarily set `RADAR_HORIZON_DAYS` to `3650` and execute up to **Normalize + Compute Notice Window**. Pick three contracts you know by hand and check `notice_deadline` equals `expiration_date` minus the notice period on the paper. Check that at least one contract shows `notice_source: 'assumed'` if any of your records lack a notice period. Set the horizon back to `90`.
3. **Empty-state dedup.** With `renewal_radar_log` empty, execute through **New Tier Only** and note the item count. Run the whole flow once, then execute through **New Tier Only** again — the count should now be zero, because every contract was logged at its current tier. This is the check that stops daily re-notification.
4. **Context degradation.** Delete the `vendor_context` row for one in-window contract and run that contract through **Score + Route**. Confirm `context_complete` is `false`, the recommendation is `renegotiate` (never `renew`), and `route` is `legal_escalation`.
5. **Model disagreement.** Find or force a contract where `model_agrees` is `false`. Confirm the escalation card carries the "Model recommended X; rules recommended Y" context line and that `recommendation` is the deterministic value, not the model's.
6. **Brief failure.** Temporarily point the Anthropic credential at an invalid key and run one contract. The flow must complete, `brief_error` must be populated, `rationale` must read "Model brief unavailable", and the Slack card must still post with the deterministic recommendation. Restore the key.
7. **Idempotency.** Re-run the full flow immediately. `renewal_radar_log` row count must not change, and no new Slack messages should post.
Once all seven pass, activate the trigger.
## Cost
Per contract that enters the radar: one Claude Sonnet 5 call at roughly 5–8k input tokens and 800–1,500 output tokens, plus adaptive thinking billed as output. At list pricing of $3 per million input and $15 per million output, that lands around $0.05–$0.07 per brief. A 400-contract vendor portfolio typically pushes 30–40 contracts across a tier boundary in a month, so inference runs $2–3 monthly.
n8n execution cost is a rounding error here and worth understanding, because it is the opposite of a per-item webhook flow: the fan-out across contracts happens *inside* one workflow run, so a weekday cron is about 22 executions a month. n8n Cloud Starter is €24/month for 2,500 executions, with unlimited workflows and users on every Cloud plan as of 2026 — this flow uses under 1% of the entry tier.
The real cost is getting notice periods out of the paper and into the repository. Budget that as the project, not the automation.
## Known limits
- **Ironclad pagination is capped** at 50 pages of 100 records (5,000 records). Raise `maxRequestsFetched` on **Ironclad — List Records** if your repository is larger, and re-check the dropped-record count the Normalize node logs.
- **The flow reads; it never writes back to the CLM.** Decisions are recorded by a human in `renewal_decisions`. Wiring the decision back into Ironclad or Docusign is a deliberate omission — a write scope on the contract repository is a much larger security review than a read scope, and the flow's value does not depend on it.
- **No holiday or business-day handling.** `days_to_deadline` counts calendar days. If your notice clauses specify business days, the deadline this flow computes is later than the real one, and you should shorten the tiers to compensate.
- **Not runtime-tested against a live Ironclad or Docusign tenant.** The endpoint paths, scopes, and provision field names come from vendor documentation; the response-shape handling in the Normalize node is defensive but you should expect to adjust the field extraction on first contact with your own data.
-- Contract Renewal Radar — supporting tables
-- Postgres 13+. Run this before importing the workflow.
-- ---------------------------------------------------------------------------
-- vendor_context: the spend and usage data your CLM does not hold.
-- Populate from your AP export, the vendor's admin API, or your IdP. One row
-- per contract, keyed by the same contract_id the flow builds
-- ("ironclad:<record id>" or "docusign:<agreement id>").
--
-- Every column is nullable on purpose. A contract with no row here still gets
-- a brief; it is flagged context_complete: false and routed for review rather
-- than silently recommended for renewal.
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS vendor_context (
contract_id text PRIMARY KEY,
licensed_seats integer,
active_seats_30d integer,
last_term_annual_cents bigint,
quoted_renewal_annual_cents bigint,
open_support_tickets_90d integer,
replacement_effort text
CHECK (replacement_effort IN ('low', 'medium', 'high')),
updated_at timestamptz NOT NULL DEFAULT now()
);
-- ---------------------------------------------------------------------------
-- renewal_radar_log: one row per contract, overwritten each time the contract
-- crosses into a new tier. This is both the audit trail and the deduplication
-- key — last_tier_notified is what stops the flow re-posting the same contract
-- every weekday for 90 days.
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS renewal_radar_log (
contract_id text PRIMARY KEY,
source text NOT NULL,
counterparty text,
notice_deadline date NOT NULL,
expiration_date date NOT NULL,
tier text NOT NULL,
days_to_deadline integer NOT NULL,
notice_source text NOT NULL
CHECK (notice_source IN ('contract', 'assumed')),
recommendation text NOT NULL
CHECK (recommendation IN ('renew', 'renegotiate', 'terminate')),
model_recommendation text,
model_agrees boolean,
utilization numeric(5, 4),
uplift numeric(6, 4),
route text NOT NULL,
rationale text,
last_tier_notified text NOT NULL,
last_notified_on date NOT NULL DEFAULT CURRENT_DATE
);
-- The dedup read is a full-table scan today. It stays cheap into the low
-- thousands of contracts; index it once the repository is larger than that.
CREATE INDEX IF NOT EXISTS renewal_radar_log_deadline_idx
ON renewal_radar_log (notice_deadline);
-- ---------------------------------------------------------------------------
-- renewal_decisions: written by a human, not by the flow. This is the table
-- your success metric reads — the flow can only prove it sent a nudge, not
-- that anyone decided anything.
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS renewal_decisions (
id bigserial PRIMARY KEY,
contract_id text NOT NULL,
decided_on date NOT NULL,
notice_deadline date NOT NULL,
decision text NOT NULL
CHECK (decision IN ('renewed', 'renegotiated', 'terminated', 'lapsed')),
decided_by text,
notes text
);
-- The number that matters: what share of decisions landed before the notice
-- deadline rather than after it.
--
-- SELECT date_trunc('quarter', decided_on) AS quarter,
-- count(*) FILTER (WHERE decided_on <= notice_deadline)::numeric
-- / count(*) AS on_time_rate
-- FROM renewal_decisions
-- GROUP BY 1 ORDER BY 1;