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.

  1. Base e formato
  2. Autenticação
    1. Gerenciar suas chaves
  3. Convenções
  4. Clientes
    1. GET /external/clients — listar clientes
    2. POST /external/clients — criar ou atualizar cliente
  5. Propostas
    1. GET /external/proposals — listar propostas
    2. GET /external/proposals/{id} — detalhe da proposta
    3. POST /external/proposals — criar ou atualizar proposta
      1. Campos de topo
      2. O objeto content (blocos)
      3. O bloco pricing
      4. Exemplo mínimo
  6. Webhooks
  7. Erros
  8. Exemplo de automação (n8n)

Base e formato

  • Base da API: https://SEU-SITE/wp-json/v3rprop/v1
  • Formato: JSON em requisições e respostas. Envie Content-Type: application/json nos POST.
  • 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 401 com {"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 em X-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: nos POST, enviar id de um registro existente atualiza; omitir id (ou 0) cria um novo.
  • Listas retornam tudo: os GET de 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 e-mail 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 status válido nasce como draft (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 e-mail 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 content complexo, monte a proposta no painel, chame GET /external/proposals/{id} e reaproveite o objeto content retornado no seu POST. 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/clients para garantir o cliente, (2) POST /external/proposals com client_id, status: "active" e notify_client: true para gerar e enviar a proposta já preenchida. Depois, quando o webhook proposal_accepted chegar, atualize o CRM e avise o time de entrega. Da oportunidade ao fechamento sem digitação manual.