Integração · documentação
Integração de dados com o RadPrice
Para equipes de TI e dados de empresas assinantes. A integração é contratada com o time de vendas; depois disso a Datatoms envia a sua chave (que aparece uma única vez) e esta documentação.
Visão geral
Seus produtos e preços aparecem na busca do RadPrice como Patrocinado, com a loja, a cidade e o link para o seu site. Eles nunca entram nas médias, rankings ou séries oficiais. Há dois modos, definidos no contrato:
- Envio (padrão): o script lê a view do seu DW e envia para o RadPrice em lotes — completo uma vez por dia e incremental no intervalo combinado. A busca responde em milissegundos.
- Direto: os dados não saem do seu ambiente. A cada busca, o RadPrice consulta o seu servidor (o mesmo script, em modo “serve”) com uma assinatura digital; a resposta fica só em memória por alguns minutos. Seu servidor precisa responder em menos de 900 ms.
Arquitetura recomendada (medalhão simplificado)
Nada acessa o seu sistema de produção. O banco oficial (ERP/PDV) alimenta o DW como já acontece hoje; uma view no DW é o contrato com o RadPrice; o script lê só essa view, com um usuário somente leitura.
Colunas da view
Nome padrão da view: radprice_ofertas (mude em DW_VIEW). Uma linha por produto em cada loja (se as lojas têm preços diferentes) ou uma linha por produto. Colunas com outros nomes são ignoradas.
| Coluna | Tipo | Obrigatória | Regra |
|---|---|---|---|
| id_externo | texto (até 80) | sim | Identificador único da linha no seu sistema. Com várias lojas, combine produto + loja (ex.: "1001-ALDEOTA"). |
| nome | texto (2–200) | sim | Nome do produto como o cliente procura: "Cimento Portland CP II-E-32 saco 50kg". |
| preco | decimal | sim | Preço à vista em reais, maior que zero. Aceita 38.90, 38,90 ou NUMERIC do banco. |
| cidade | texto | sim | Cidade da loja. |
| uf | texto (2) | sim | Sigla do estado: CE, SP… |
| marca | texto (até 80) | não | Fabricante/marca. Entra na busca. |
| categoria | texto (até 80) | não | Categoria do seu catálogo. Entra na busca. |
| unidade | texto (até 60) | não | Embalagem/unidade exibida: "saco 50 kg", "m³", "un". |
| gtin | texto | não | EAN/GTIN com 8, 12, 13 ou 14 dígitos. |
| sku | texto (até 60) | não | Código interno. |
| preco_de | decimal | não | Preço "de" (antes da promoção), se houver. |
| estoque | texto | não | em_estoque, sob_encomenda ou sem_estoque. |
| link_produto | texto https | não | Página do produto no SEU domínio. Sem link, o clique leva ao site cadastrado. |
| loja | texto (até 120) | não | Nome da filial: "Loja Aldeota". |
| endereco | texto (até 200) | não | Endereço da loja. |
| codigo_ibge | texto (7) | não | Código IBGE do município. |
| latitude / longitude | decimal | não | Posição da loja (graus decimais). |
| atualizado_em | data e hora | não* | Quando o preço mudou. *Obrigatória para o envio incremental. |
| ativo | 0/1 ou booleano | não | 0/false remove o item do RadPrice no próximo envio. |
| busca | texto | não | Só modo direto: texto em minúsculas e sem acento para a busca (mais rápido e preciso). |
Criando a view (exemplos)
Adapte os nomes das suas tabelas da camada prata. Crie também um usuário só de leitura, com acesso apenas à view.
CREATE VIEW radprice_ofertas AS
SELECT p.codigo || '-' || l.codigo AS id_externo,
p.descricao AS nome,
p.marca, p.ean AS gtin,
p.unidade_venda AS unidade,
pr.preco_vista AS preco,
pr.preco_cheio AS preco_de,
CASE WHEN e.saldo > 0 THEN 'em_estoque' ELSE 'sob_encomenda' END AS estoque,
'https://www.suaempresa.com.br/p/' || p.slug AS link_produto,
l.nome AS loja, l.endereco, l.cidade, l.uf,
pr.alterado_em AS atualizado_em,
(p.ativo AND pr.preco_vista > 0) AS ativo,
lower(unaccent(p.descricao || ' ' || coalesce(p.marca, ''))) AS busca
FROM prata.produtos p
JOIN prata.precos pr ON pr.produto_id = p.id
JOIN prata.lojas l ON l.id = pr.loja_id
LEFT JOIN prata.estoque e ON e.produto_id = p.id AND e.loja_id = l.id;
CREATE ROLE radprice_leitor LOGIN PASSWORD '<senha forte>';
GRANT USAGE ON SCHEMA public TO radprice_leitor;
GRANT SELECT ON radprice_ofertas TO radprice_leitor;CREATE VIEW dbo.radprice_ofertas AS
SELECT CONCAT(p.codigo, '-', l.codigo) AS id_externo, p.descricao AS nome, p.marca, p.ean AS gtin,
p.unidade_venda AS unidade, pr.preco_vista AS preco, l.nome AS loja, l.cidade, l.uf,
CONCAT('https://www.suaempresa.com.br/p/', p.slug) AS link_produto,
pr.alterado_em AS atualizado_em, CASE WHEN p.ativo = 1 THEN 1 ELSE 0 END AS ativo
FROM prata.produtos p JOIN prata.precos pr ON pr.produto_id = p.id JOIN prata.lojas l ON l.id = pr.loja_id;
CREATE LOGIN radprice_leitor WITH PASSWORD = '<senha forte>';
CREATE USER radprice_leitor FOR LOGIN radprice_leitor;
GRANT SELECT ON dbo.radprice_ofertas TO radprice_leitor;CREATE VIEW radprice_ofertas AS
SELECT CONCAT(p.codigo, '-', l.codigo) AS id_externo, p.descricao AS nome, p.marca, p.ean AS gtin,
pr.preco_vista AS preco, l.nome AS loja, l.cidade, l.uf, pr.alterado_em AS atualizado_em, p.ativo
FROM produtos p JOIN precos pr ON pr.produto_id = p.id JOIN lojas l ON l.id = pr.loja_id;
CREATE USER 'radprice_leitor'@'%' IDENTIFIED BY '<senha forte>';
GRANT SELECT ON dw.radprice_ofertas TO 'radprice_leitor'@'%';Script Python
Um único arquivo, radprice_sync.py, com Python 3.10 ou mais novo. Baixe os três arquivos no menu ao lado (ou abaixo), coloque numa pasta do servidor que enxerga o DW e instale as dependências e o driver do seu banco:
python -m venv .venv
# Linux: source .venv/bin/activate Windows: .venv\Scripts\activate
pip install -r requirements.txt
pip install "psycopg[binary]" # PostgreSQL (ou pyodbc / PyMySQL / oracledb)
cp radprice.env.exemplo .env # Windows: copy radprice.env.exemplo .envRADPRICE_CHAVE=rpk_XXXXXXXX_... # enviada pela Datatoms
DW_URL=postgresql+psycopg://radprice_leitor:[email protected]:5432/dw
DW_VIEW=radprice_ofertas
RADPRICE_INTERVALO_MIN=60| Comando | O que faz |
|---|---|
| python radprice_sync.py check | Confere .env, conexão com o DW, colunas, uma amostra de 500 linhas e a chave. Rode sempre primeiro. |
| python radprice_sync.py sync | Envio completo: o que não vier na view sai do RadPrice. Envio completo vazio nunca apaga o catálogo. |
| python radprice_sync.py sync --incremental | Só as linhas com atualizado_em depois do último envio com sucesso. |
| python radprice_sync.py watch | Fica rodando: incremental a cada RADPRICE_INTERVALO_MIN e completo 1 vez por dia; erros são repetidos com espera. |
| python radprice_sync.py serve | Modo direto: responde às buscas assinadas pelo RadPrice (veja abaixo). |
O script guarda o horário do último envio com sucesso em .radprice_estado.json (apague para forçar um completo). Linhas inválidas são puladas com o motivo no log — as demais seguem.
Deixando rodando
[Unit]
Description=RadPrice sync
After=network-online.target
[Service]
WorkingDirectory=/opt/radprice
ExecStart=/opt/radprice/.venv/bin/python radprice_sync.py watch
Restart=always
RestartSec=30
User=radprice
[Install]
WantedBy=multi-user.target
# sudo systemctl enable --now radprice && journalctl -u radprice -fschtasks /Create /TN "RadPrice sync" /SC HOURLY /RU SYSTEM ^
/TR "C:\radprice\.venv\Scripts\python.exe C:\radprice\radprice_sync.py sync --incremental"
schtasks /Create /TN "RadPrice completo" /SC DAILY /ST 03:00 /RU SYSTEM ^
/TR "C:\radprice\.venv\Scripts\python.exe C:\radprice\radprice_sync.py sync"FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt radprice_sync.py ./
RUN pip install --no-cache-dir -r requirements.txt "psycopg[binary]"
CMD ["python", "radprice_sync.py", "watch"]
# docker run -d --restart=always --env-file .env radprice-syncComo verificar se está funcionando
python radprice_sync.py checkdeve terminar com “Tudo certo.” — cada linha mostra[ok],[aviso]ou[erro]com o motivo.- Depois de um
sync, o log mostra quantos itens foram gravados, removidos e recusados, e o total no RadPrice. - O
checktambém mostra o último envio registrado no RadPrice e se a integração já está visível na busca (situação “ativa”). - No site, abra a busca (lupa ou tecla /) e digite um produto seu: ele aparece no bloco “Patrocinado”.
- Modo direto:
GET /saudeno seu servidor responde {"ok": true}; cada busca recebida aparece no log com o tempo de resposta.
Modo direto (sem enviar os dados)
Rode python radprice_sync.py serve e publique a porta local (padrão 127.0.0.1:8787) atrás de um HTTPS público — Nginx, Caddy, IIS ou um túnel (ex.: Cloudflare Tunnel). Informe o endereço público ao comercial, por exemplo https://radprice.suaempresa.com.br/radprice/buscar.
GET /radprice/buscar?q=cimento+cp+ii+32&uf=CE&limit=5
x-radprice-timestamp: 1793102400
x-radprice-signature: <Ed25519 em base64 de "1793102400\n/radprice/buscar?q=cimento+cp+ii+32&uf=CE&limit=5">
x-radprice-key-id: <id da chave pública>{"items": [{"externalId": "1001-ALDEOTA", "name": "Cimento CP II-E-32 saco 50kg",
"price": "38.90", "city": "Fortaleza", "uf": "CE", "store": "Loja Aldeota",
"url": "https://www.suaempresa.com.br/p/cimento-cp2"}]}O script confere a assinatura com a chave pública do RadPrice (/api/v1/integrations/public-key, ou fixada em RADPRICE_CHAVE_PUBLICA_PEM) e recusa pedidos com mais de 60 segundos. Se um proxy mudar o caminho, informe o caminho público em RADPRICE_CAMINHO_PUBLICO. Para a busca ficar rápida, crie a coluna busca (minúsculas, sem acento) e um índice nela. Fora do ar ou lento, sua empresa simplesmente não aparece naquela busca.
Referência da API (para quem não usar o script)
Base https://radprice.datatoms.com/api/v1, cabeçalho Authorization: Bearer rpk_…, JSON com os mesmos campos da view em inglês (externalId, name, brand, gtin, sku, category, unit, price, regularPrice, stock, url, store, address, city, uf, ibgeCode, lat, lon, updatedAt). Preço sempre em texto decimal com ponto.
GET /integrations/me situação, plano, itens e último envio
POST /integrations/sync {"kind":"full"|"incremental"} -> {"runId"}
POST /integrations/sync/{runId}/items {"items":[…até 1000], "remove":["id_externo", …]}
-> {"accepted", "removed", "rejected":[{"index","externalId","error"}]}
POST /integrations/sync/{runId}/finish {} -> {"upserted", "removed", "total"}Segurança
- A chave é exclusiva da sua empresa e o RadPrice guarda só o hash (SHA-256). Trate como senha: só no .env/cofre, nunca no git ou em e-mail aberto.
- Vazou ou trocou a equipe? Peça a revogação: a chave para de funcionar na hora e outra é gerada.
- Somente HTTPS. Com IP fixo de saída, peça para liberar só esse IP (outros recebem 403).
- O usuário do banco lê apenas a view. O RadPrice nunca acessa o seu banco — quem conecta é o script, de dentro da sua rede.
- Links de produto só no domínio cadastrado; dados de clientes, custos ou margens nunca devem estar na view.
- Modo direto: consultas assinadas (Ed25519) e com validade de 60 s; o resto é recusado. Nada é gravado no RadPrice.
- Os dados aparecem sempre como “Patrocinado” e não alteram médias, rankings nem dados oficiais.
Erros comuns
| 401 INVALID_KEY | Chave errada, incompleta ou revogada. Confira RADPRICE_CHAVE (começa com rpk_) ou peça uma nova. |
| 403 IP_NOT_ALLOWED | O IP de saída do servidor não está na lista liberada. Informe o IP fixo ao comercial. |
| 403 PARTNER_CANCELED | Integração cancelada. Fale com [email protected]. |
| 409 DIRECT_MODE | Seu plano é modo direto: rode "serve", não "sync". |
| 409 RUN_CLOSED | O envio expirou (mais de 6 h) ou já foi encerrado. O script começa outro sozinho. |
| 422 EMPTY_FULL_SYNC | Envio completo sem nenhum item válido. O catálogo anterior foi mantido; rode "check". |
| 429 | Limite de 240 requisições por minuto por chave. O script espera e tenta de novo. |
| url: link fora do domínio | link_produto aponta para outro site. Use o domínio cadastrado (o check mostra quais valem). |
| price: … | Preço vazio, zero ou com texto. Use número (38.90) ou texto com vírgula (38,90). |
Dúvidas: [email protected].