Pular para conteúdo

API da PIED — REST e Webhook

Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-07-11

Referência técnica dos dois canais de entrada do maxsul-pied. Contrato validado contra o ambiente real em 2026-06-17.

1. Acesso

  • Base URL v2: https://backend-pied-prod.piedadmin.com.br/api/v2 (produtos, pedidos).
  • Base URL v1: https://backend-pied-prod.piedadmin.com.br/api/v1 (clientes/companies).
  • Autenticação: header Authorization: Bearer <token>. O token de produção é provido por variável de ambiente do Coolify (PIED_TOKEN), nunca no código; o poll nasce desligado até ele existir.
  • Envelope de resposta: { "error": null, "data": { "items": [ … ], "totalItems": N } }.
  • Paginação: rota /{recurso}/{página}/{limite}, 1-based, limite máx. 50. Página além do fim retorna HTTP 200 com items vazio (condição de parada); 204 = nenhum registro.
  • Limite de uso: dois tetos por hora (nº de chamadas e tempo de processamento). Estourar → 429 com hardLimit/timeLimit no corpo → backoff; em timeLimit, esperar a virada da janela.

2. REST

2.1 Produtos — GET /api/v2/equipments/{página}/{limite}

Varredura completa (sem delta). Upsert por productCode.

{ "productCode": "MOD-550", "name": "Painel Solar 550W Monocristalino",
  "manufacturer": "Canadian Solar", "model": "CS6W-550MS", "type": "module",
  "baseprice": 780.00, "inventory": { "current": 320, "reserved": 12, "minimum": 50 } }

2.2 Clientes — GET /api/v1/companies/{página}/{limite} (v1)

Mesmo objeto company do pedido (cnpj/cpf, companyName, mainContact, address) + stateInscription. Varredura completa. Use para clientes que ainda não geraram pedido (os demais vêm embutidos nos pedidos).

2.3 Pedidos — GET /api/v2/requests/order/{página}/{limite}[?lastUpdateAfter=AAAA-MM-DD]

Cliente (company) e itens (products) vêm embutidos. Orçamentos usam GET /api/v2/requests/budget/… com o mesmo formato (uso interno — não vão ao X-Adm).

{
  "id": "6f3a9c12", "code": "200000123", "type": "order",
  "kind": "Kit Personalizado", "totalPower": 5.5,
  "dealStatus": "Novo", "originalValue": 25000, "discount": 5, "finalValue": 23750,
  "company": {
    "cnpj": "12.345.678/0001-09", "companyName": "Solar Sul Ltda",
    "mainContact": { "name": "João", "surname": "Silva", "email": "joao@solarsul.com", "cellphone": "5642999990000" },
    "address": { "CEP": "84010-000", "state": "PR", "city": "Ponta Grossa",
                 "neighborhood": "Centro", "patio": "Rua das Acácias", "number": "100", "complement": "" }
  },
  "products": [ { "productCode": "MOD-550", "type": "module", "quantity": 10,
                  "singlePrice": 780, "totalPrice": 7800, "center": { "code": "CD001" } } ],
  "payment": { "type": "credito", "status": "received", "paidAt": "2026-08-18T16:00:38.620Z",
               "transactionId": "fb35…", "transactionStatus": "approved", "links": [],
               "condition": { "name": "30/60 dias", "quantityOfInstallments": 2 } },
  "freight": { "type": "CIF", "price": 350 },
  "lastUpdate": "2026-06-15T19:12:24.391Z"
}

Pagamento — um status macro por pedido. O payment é um objeto único (não um array): um status, um paidAt, uma condition. Verificado em 1409 objetos capturados. Valores de status: notRequested · requested · partial · received · cancelled.

  • paidAt = quando entrou o primeiro dinheiro; permanece preenchido mesmo após cancelled (é histórico) — para saber o estado atual olhe status, não paidAt.
  • O status individualizado por parcela/forma (o que a tela "Pagamento" da PIED mostra por linha) não vem embutido: só via payment.links[] (GET /api/v2/payments/{id}, um por lançamento — esparso, presente em ~4% dos pedidos). A integração não consome esse sub-recurso hoje.
  • O valor da NF independe do pagamento: finalValue/originalValue/invoice.serviceTotal/ produtos são idênticos em partial e received (ver decisão 0010).

2.4 Incremental

  • ?lastUpdateAfter=AAAA-MM-DD só tem efeito em pedidos (produtos/clientes são varridos por inteiro). Granularidade de dia → a janela do último dia é reprocessada de propósito (dedup é da fase 2). O cursor vive em pied_cursor (Modelagem §2.1).
  • A documentação da PIED cita um delta implícito por apiSent/apiSentUpdated sem parâmetros — não confirmado no ambiente real; o mecanismo confiável é o lastUpdateAfter.

2.5 Erros

401/403 aborta a entidade; 429/5xx/timeout → retentativa com espera progressiva; resposta não-JSON → falha controlada.

3. Webhook (PIED → nós)

POST HTTPS para a URL configurada, corpo { "event": …, "data": { … } }. O data é completo (mesmo formato do pedido REST — em geral dispensa segunda consulta).

Eventos: budget.created · budget.updated · order.created · order.updated. Empresas também disparam evento, mas sem nome publicado (a confirmar). Não há webhook de produto.

{ "event": "order.created",
  "data": { "id": "6f3a9c12", "code": "200000123", "type": "order", "kind": "Kit Personalizado",
            "totalPower": 5.5, "dealStatus": "Novo", "finalValue": 23750,
            "company": { "cnpj": "12.345.678/0001-09", "companyName": "Solar Sul Ltda", "…": "…" },
            "products": [ { "productCode": "MOD-550", "quantity": 10, "singlePrice": 780 } ],
            "payment": { "type": "credito", "status": "received" },
            "lastUpdate": "2026-06-15T19:12:24.391Z" } }

Resposta: 200 OK rápido (só confirma o recebimento; processamento assíncrono).

3.1 Lacunas (NÃO disparam webhook)

Alteração de responsável, edição de frete, campos customizados, anotações, upload de arquivos. Por isso o webhook não é espelho fiel — o que falta é coberto pela reconciliação REST (§2.3). É por isso que o poll REST é obrigatório, não rede de segurança.

3.2 Segurança (a confirmar com a PIED)

  • Sem HMAC/assinatura documentada → segredo compartilhado (header X-Pied-Secret, a confirmar) + IP allowlist no Traefik + HTTPS.
  • Política de reenvio não documentada → assumir "pode perder evento"; a reconciliação REST cobre.

3.3 Cadastro do webhook (manual, painel da PIED)

Configurações → API e Webhooks → aba Webhooks → Adicionar: endpoint https://pied.maxsul.xadm.biz/webhook/pied, gatilhos budget.*/order.*, segredo. Testar com webhook.site/ngrok antes de apontar para produção. Ver Configuração.