API REST e Automações
Referência completa da API REST do V3RProp para integrar suas propostas a ferramentas de automação (n8n, Make, Zapier) ou ao seu próprio sistema — por exemplo, criar uma proposta automaticamente quando um lead avança no CRM e ser avisado no instante em que o cliente aceita.
Para quem é esta página
Esta é a única seção técnica do manual, voltada a quem monta integrações. Se você só usa o painel, pode pular — nada aqui é obrigatório para o dia a dia.
Base e formato
- Base da API:
https://SEU-SITE/wp-json/v3rprop/v1 - Formato: JSON em requisições e respostas. Envie
Content-Type: application/jsonnosPOST. - Fuso e moeda: datas seguem o fuso do WordPress; valores monetários são números decimais (ex.:
12000.00), aceitos também como texto no formato brasileiro ("1.234,56") ou americano ("1234.56").
Autenticação
Gere uma ou mais chaves de API em Configurações → Configurações Técnicas. Envie uma delas em todas as requisições, de preferência no cabeçalho HTTP:
X-V3RProp-API-Key: SUA_CHAVE_DE_API
Alternativamente, como parâmetro de URL (funciona, mas evite: a chave fica exposta em logs de servidor e no histórico do navegador):
GET /wp-json/v3rprop/v1/external/clients?api_key=SUA_CHAVE_DE_API
Qualquer chave ativa autentica — a API aceita todas as chaves da lista. Isso permite emitir uma chave por integração (por exemplo, uma para o n8n e outra para o seu sistema) e revogar apenas uma delas quando precisar, sem derrubar as demais.
Gerenciar suas chaves
Na aba Técnico das Configurações, cada chave aparece em uma linha, onde você pode:
- Adicionar uma nova chave e dar a ela um rótulo (ex.: “n8n — CRM”, “Integração interna”) para saber de onde vem cada acesso.
- Copiar ou mostrar/ocultar o valor da chave.
- Revogar uma chave: ao removê-la da lista, ela deixa de autenticar imediatamente; as outras continuam valendo.
- Acompanhar o uso de cada chave — o painel mostra “Usada N× · último uso”, atualizado a cada requisição autenticada, para você identificar chaves ociosas ou confirmar que uma integração está ativa.
Se você já usava a chave única de versões anteriores, ela é preservada automaticamente como a entrada “Chave padrão” — suas integrações atuais continuam funcionando sem nenhuma alteração.
Sem chave válida, a API responde
401com{"code":"rest_forbidden"}. Confira se a chave existe na lista das Configurações (e não foi revogada) e se está sendo enviada exatamente como o cabeçalho acima (com o hífen emX-V3RProp-API-Key).
Convenções
- Só 5 endpoints externos, todos sob
/external/. Os demais endpoints do plugin são internos ao painel (exigem login) e não fazem parte desta API. - Cria ou atualiza pelo
id: nosPOST, enviaridde um registro existente atualiza; omitirid(ou0) cria um novo. - Listas retornam tudo: os
GETde lista não têm paginação nem filtro — devolvem todos os registros de uma vez. - Trate os campos como obrigatórios: a API não valida a maioria dos campos na entrada. Omitir um campo esperado não retorna erro — grava vazio e pode gerar um registro inconsistente. Envie sempre o conjunto completo. As exceções que retornam erro estão marcadas em cada endpoint.
Clientes
GET /external/clients — listar clientes
Sem parâmetros. Retorna todos os clientes cadastrados, em ordem alfabética.
Resposta — 200:
[
{
"id": 45,
"name": "ACME Ltda",
"cnpj": "12.345.678/0001-90",
"website": "https://acme.com",
"contact_name": "João Silva",
"email": "joao@acme.com",
"phone": "(61) 99999-0000",
"address": "SCS Quadra 1, Brasília-DF",
"notes": "Cliente desde 2024.",
"logo": "https://seu-site/wp-content/uploads/acme.png"
}
]
Todos os campos são texto, exceto id (inteiro).
POST /external/clients — criar ou atualizar cliente
Corpo:
| Campo | Tipo | Obrigatório | Observação |
|---|---|---|---|
id |
inteiro | não | Presente ⇒ atualiza; ausente/0 ⇒ cria. |
name |
texto | sim | Razão social / nome do cliente. |
cnpj |
texto | não | Aceita CPF ou CNPJ. Se tiver formato de CNPJ e o dígito verificador for inválido, retorna 400 (ver Erros). |
website |
URL | sim | |
contact_name |
texto | sim | Nome do contato. |
email |
sim | ||
phone |
texto | sim | |
address |
texto | sim | |
notes |
texto | sim | Observações internas. |
logo |
URL | sim | URL de uma imagem já hospedada (a API não faz upload). |
Exemplo:
curl -X POST "https://SEU-SITE/wp-json/v3rprop/v1/external/clients" \
-H "X-V3RProp-API-Key: SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"name": "ACME Ltda",
"cnpj": "12.345.678/0001-90",
"email": "joao@acme.com",
"contact_name": "João Silva",
"website": "https://acme.com",
"phone": "(61) 99999-0000",
"address": "SCS Quadra 1, Brasília-DF",
"notes": "",
"logo": ""
}'
Resposta — 200: { "success": true, "id": 45 }
Propostas
GET /external/proposals — listar propostas
Sem parâmetros. Retorna todas as propostas (rascunhos, ativas, aceitas, rejeitadas, expiradas), da mais recente para a mais antiga. Versões antigas de uma proposta (substituídas) não aparecem.
Resposta — 200:
[
{
"id": 123,
"title": "Proposta Website Institucional",
"slug": "proposta-website-institucional",
"client_name": "ACME Ltda",
"version": "v1",
"status": "active",
"date": "28/07/2026 14:30",
"value": 12000.00,
"one_off_total": 12000.00,
"mrr": 990.00,
"link": "https://seu-site/v3rprop-proposta/proposta-website-institucional/",
"pdf_url": ""
}
]
| Campo | Tipo | Observação |
|---|---|---|
status |
texto | draft, active, accepted, rejected ou expired. |
value / one_off_total |
decimal | Total pontual (à vista). value é um alias de compatibilidade. |
mrr |
decimal | Receita recorrente mensal normalizada (trimestral ÷ 3, anual ÷ 12). |
link |
URL | Página pública da proposta. |
pdf_url |
URL | Preenchido apenas quando status = accepted (link do PDF assinado). Para outros status, vem "". |
GET /external/proposals/{id} — detalhe da proposta
Retorna a proposta completa, incluindo conteúdo, precificação, histórico de versões, visualizações e comentários.
Resposta — 200 (resumida):
{
"id": 123,
"title": "Proposta Website Institucional",
"status": "active",
"link": "https://seu-site/v3rprop-proposta/…/",
"client_id": 45,
"client_name": "ACME Ltda",
"client_email": "joao@acme.com",
"client_logo": "https://…",
"validity_days": 30,
"show_scarcity_timer": true,
"layout_mode": "standalone",
"password": "PROTECTED", // "PROTECTED" se tem senha, "" se não — nunca o valor
"color_primary": "#0b280b",
"color_secondary": "#2d5016",
"color_accent": "#ffdd55",
"font_family": "Inter",
"version": "v1",
"content": { /* envelope de blocos — ver POST abaixo */ },
"accept_data": null, // ou os dados do aceite eletrônico
"version_history": [ { "id": 130, "version": "v2", "date": "…", "status": "superseded" } ],
"view_logs": [ { "ip_address": "…", "user_agent": "…", "viewed_at": "…" } ],
"comments": [ { "id": 9, "author": "…", "email": "…", "content": "…", "date": "…", "is_admin": false } ]
}
accept_data, quando a proposta foi aceita, traz { name, role, email, document, notes, timestamp, ip, ua }.
Erro — 404: { "code": "proposal_not_found", "message": "Proposta comercial não encontrada." } quando o id não existe.
POST /external/proposals — criar ou atualizar proposta
O endpoint mais rico. Enviar id atualiza; omitir cria (nasce como v1).
Uma proposta criada sem
statusválido nasce comodraft(rascunho) e não fica visível ao cliente. Para publicar, envie"status": "active".
Campos de topo
| Campo | Tipo | Obrigatório | Observação |
|---|---|---|---|
id |
inteiro | não | Presente ⇒ atualiza; ausente ⇒ cria. |
title |
texto | sim | Título interno da proposta. |
status |
texto | recomendado | draft, active, accepted, rejected, expired. Valor ausente/inválido ⇒ draft. |
content |
objeto | sim | Conteúdo da proposta (blocos). É a fonte de verdade do que o cliente vê. Ver schema abaixo. |
client_id |
inteiro | condicional | > 0 vincula a um cliente existente e copia nome/e-mail/logo/CNPJ dele. Não cria cliente — crie antes via /external/clients. |
client_name |
texto | condicional | Usado apenas se client_id não for enviado. |
client_email |
não | Idem (só sem client_id). |
|
client_logo |
URL | não | Idem. |
validity_days |
inteiro | sim | Prazo de validade, em dias. |
layout_mode |
texto | sim | Modo de exibição da página pública (ex.: standalone). |
color_primary |
texto | sim | Cor primária (hex, ex.: #0b280b). |
color_secondary |
texto | sim | Cor secundária. |
color_accent |
texto | sim | Cor de destaque. |
font_family |
texto | não | Default "Inter". |
show_scarcity_timer |
booleano | não | Contador de escassez. Default true. |
password |
texto | não | Define/remove senha da proposta. Texto ⇒ define; "" ⇒ remove; "PROTECTED" ⇒ mantém a atual (útil ao reenviar um objeto vindo do GET). |
notify_client |
booleano | não | Se true e o status for active, o V3RProp envia o e-mail da proposta ao cliente. |
O objeto content (blocos)
content é um envelope versionado. A ordem dos blocos no array é a ordem em que aparecem na proposta.
{
"schema_version": 3,
"model": "classico", // "classico" ou "moderno"
"header_style": { "width": "boxed", "bgColor": "", "textColor": "", "logoSize": "medium" },
"footer_style": { "width": "boxed", "bgColor": "", "textColor": "", "logoSize": "medium" },
"default_module_icon": "grid",
"blocks": [ /* blocos tipados, na ordem de exibição */ ]
}
Cada bloco tem type e os campos do seu tipo. Tipos desconhecidos são descartados silenciosamente. Tipos aceitos e seus campos principais:
type |
Para quê | Campos principais |
|---|---|---|
hero |
Capa da proposta | title, kicker, subtitle, background_url, overlay (none/dark/gradient), cover_use_background (bool) |
richtext |
Texto livre | content (HTML), preset (plain/highlight/note/warning), title, icon, image_url |
products |
Produtos e serviços | columns (2/3/4), modules[] — cada um com title, description (HTML), features[], market_value, proposed_value, photo_url, logo_url, link_url |
pricing |
Precificação | pricing — envelope próprio (ver abaixo) |
timeline |
Cronograma | months[] — cada um com month, title, tags, result |
reasons |
Razões para a parceria | columns, reasons[] — title, description (HTML) |
grid |
Grade de cards | columns, cards[] — title, text (HTML), icon, image_url |
callout |
Destaque / depoimento | variant (box/quote), text (HTML), attribution, image_url |
comparison |
Comparativo de investimento | market_value, our_value, economy_label, note |
O bloco pricing
O campo pricing de um bloco pricing é um envelope próprio. O servidor sempre recalcula os totais — não confie em enviar totais prontos.
{
"schema_version": 2,
"summary": { "one_off": "auto", "recurring": "auto" }, // por card: auto | show | hide
"blocks": [
{
"type": "sum", // sum | setup | recurring | options
"title": "Investimento",
"items": [
{
"description": "Desenvolvimento do site",
"nature": "one_off", // one_off | hourly | per_event | on_request
"market_value": 15000.00,
"proposed_value": 12000.00,
"quantity": 1
}
]
},
{
"type": "recurring",
"title": "Manutenção mensal",
"value_nature": "fixed", // fixed | variable
"amount": 990.00,
"periodicity": "monthly", // monthly | quarterly | yearly
"duration_type": "indeterminate"
}
]
}
O bloco options (planos selecionáveis) usa selection_mode (single/multi) e um array options[], cada opção com name, services[] e um recurring opcional aninhado. Para o desenho completo, monte uma proposta no editor e leia o resultado via GET /external/proposals/{id} — o content retornado é exatamente o formato aceito no POST.
Exemplo mínimo
curl -X POST "https://SEU-SITE/wp-json/v3rprop/v1/external/proposals" \
-H "X-V3RProp-API-Key: SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{
"title": "Website Institucional — ACME",
"status": "active",
"client_id": 45,
"validity_days": 15,
"layout_mode": "standalone",
"color_primary": "#0b280b",
"color_secondary": "#2d5016",
"color_accent": "#ffdd55",
"notify_client": true,
"content": {
"schema_version": 3,
"model": "classico",
"blocks": [
{ "type": "hero", "title": "Sua nova presença digital", "subtitle": "Proposta para a ACME" },
{ "type": "richtext", "content": "<p>Obrigado pela oportunidade...</p>" },
{ "type": "pricing", "pricing": {
"schema_version": 2,
"blocks": [
{ "type": "sum", "title": "Investimento",
"items": [ { "description": "Site institucional", "nature": "one_off", "proposed_value": 12000.00, "quantity": 1 } ] }
]
} }
]
}
}'
Resposta — 200: { "success": true, "id": 123 }
Dica de fluxo: para descobrir o formato exato de um
contentcomplexo, monte a proposta no painel, chameGET /external/proposals/{id}e reaproveite o objetocontentretornado no seuPOST. Ao reenviar, mantenha"password": "PROTECTED"para não apagar a senha.
Webhooks
Além de receber comandos, o V3RProp avisa um sistema externo quando algo acontece com uma proposta. Configure uma URL de webhook em Configurações (campo único, global). Se estiver vazia, nada é enviado.
Eventos:
event |
Quando dispara |
|---|---|
proposal_viewed |
Na primeira visualização da proposta pelo cliente (só na primeira). |
proposal_accepted |
Quando o cliente aceita a proposta. |
proposal_rejected |
Quando o cliente rejeita ou pede alterações. |
Payload (POST JSON):
{
"event": "proposal_accepted",
"timestamp": "2026-07-28T14:30:00-03:00",
"proposal_id": 123,
"proposal": {
"title": "Website Institucional — ACME",
"status": "accepted",
"client_name": "ACME Ltda",
"total_value": 12000.00, // = total pontual (compatibilidade)
"pricing_summary": { "one_off_total": 12000.00, "mrr": 990.00 },
"url": "https://seu-site/v3rprop-proposta/…/"
},
"data": { /* varia por evento */ }
}
O objeto data muda conforme o evento:
proposal_viewed→{ "ip": "…", "ua": "…" }proposal_accepted→{ "name", "role", "email", "document", "notes", "timestamp", "ip", "ua" }(dados do aceite eletrônico)proposal_rejected→{ "name", "email", "reason" }
Entrega: o webhook é enviado uma vez por evento (sem novas tentativas), com timeout de 5 segundos, e não vai assinado (sem HMAC). Se a entrega garantida for importante para você, registre o recebimento e concilie periodicamente via
GET /external/proposals.
Erros
| HTTP | code |
Quando |
|---|---|---|
401 |
rest_forbidden |
Chave de API ausente, inválida ou não configurada. |
400 |
invalid_cnpj |
POST /external/clients com um CNPJ de dígito verificador inválido. |
404 |
proposal_not_found |
GET /external/proposals/{id} com id inexistente. |
Respostas de erro seguem o padrão do WordPress REST: { "code": "...", "message": "...", "data": { "status": 4xx } }.
Exemplo de automação (n8n)
Ao ganhar um negócio no CRM: (1)
POST /external/clientspara garantir o cliente, (2)POST /external/proposalscomclient_id,status: "active"enotify_client: truepara gerar e enviar a proposta já preenchida. Depois, quando o webhookproposal_acceptedchegar, atualize o CRM e avise o time de entrega. Da oportunidade ao fechamento sem digitação manual.