Documentação da API
Emita certificados de autenticidade a cada venda e permita que qualquer pessoa confira, com prova pública em blockchain. Base URL: https://SEU-DOMINIO. Todas as respostas são JSON (exceto QR Code e CSV).
Começando em 3 passos
- Peça o acesso ao painel e crie uma chave de API em API e chaves.
- Faça um POST por venda em
/api/certificates(ou conecte Shopify/WooCommerce sem código, em Conectar loja / ERP). - Coloque o
verifyUrl/ QR Code na etiqueta, e o widget no seu site.
curl -X POST https://SEU-DOMINIO/api/certificates \
-H "Authorization: Bearer rk_live_SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"nf":"NF 000482","sku":"BLS-2291","desc":"Bolsa Atelier","ownerName":"Maria Souza","ownerEmail":"maria@exemplo.com"}'Autenticação
Envie Authorization: Bearer <chave>. Há três tipos de credencial:
| Credencial | Formato | Uso |
|---|---|---|
| Chave de API da marca | rk_live_… | Seu servidor/ERP. Escopos: certificates:read, certificates:write. Só o hash fica guardado; a chave completa aparece uma única vez, na criação. |
| Login do painel (JWT) | eyJ… | Painel. Também acessa administração (chaves, ERP, lojas). |
| Loja parceira | headers x-retailer-code, x-retailer-key, x-brand-slug | Somente emitir (POST /api/certificates). |
Nunca coloque a chave em JavaScript de navegador. A consulta pública e o widget não precisam de chave.
Emitir certificado
POST /api/certificates — permissão certificates:write.
| Campo | Tipo | |
|---|---|---|
nf | texto ≤ 60 | obrigatório — nota fiscal ou pedido |
sku | texto ≤ 60 | obrigatório — código do produto |
desc | texto ≤ 200 | opcional — descrição exibida no certificado |
ownerName | texto ≤ 120 | obrigatório — proprietário atual |
ownerEmail | obrigatório — necessário para transferência de titularidade | |
accent | #RRGGBB | opcional — cor do certificado (padrão: cor da marca) |
Resposta 201:
{
"code": "RM-7F2K9CX1M4QA",
"status": "active",
"brand": "Atelier Marin",
"sku": "BLS-2291", "nf": "NF 000482",
"ownerName": "Maria Souza", "ownerEmail": "maria@exemplo.com",
"issuedAt": "2026-09-20T18:02:11.412Z",
"issuedVia": { "type": "official" },
"tokenId": "4559225325…", "dataHash": "0xee2b1bbf…", "txHash": "0x507f28be…",
"txUrl": "https://polygonscan.com/tx/0x507f28be…",
"verifyUrl": "https://SEU-DOMINIO/v/RM-7F2K9CX1M4QA"
}
O hash é gravado na blockchain antes de o certificado ser salvo: nunca existe certificado sem prova.
Listar e buscar
GET /api/certificates?q=&status=active|revoked&page=1&limit=25 — permissão certificates:read. Busca por código, SKU, nota, proprietário, e-mail e descrição. Resposta: { data: [...], page, limit, total }. Cada marca vê somente os próprios certificados.
Consultar (público, sem chave)
GET /api/certificates/{código} — usado pela página /v/{código}, pelo QR Code e pelo widget. O e-mail nunca é exposto e o nome vem abreviado (“Maria S.”).
{
"code": "RM-7F2K9CX1M4QA",
"status": "authentic", // authentic | revoked | tampered
"brand": { "name": "Atelier Marin", "slug": "atelier-marin" },
"product": { "sku": "BLS-2291", "description": "Bolsa Atelier" },
"ownerName": "Maria S.",
"history": [ { "event": "Emitido", "owner": "Maria S.", "date": "…", "txUrl": "…" } ],
"verification": { "valid": true, "revoked": false, "onChainHash": "0x…", "expectedHash": "0x…" },
"proof": { "mode": "onchain", "network": "Polygon", "contractAddress": "0x…", "txUrl": "…" }
}
| status | Significa |
|---|---|
authentic | O registro confere com o último hash on-chain e não foi revogado. |
revoked | A marca revogou o certificado (falsificação, roubo, devolução). |
tampered | Os dados não conferem com a blockchain: registro alterado fora do fluxo oficial. |
Também: GET /api/certificates/{código}/proof (dados para conferência independente) e GET /api/certificates/{código}/qr.svg (QR Code vetorial que aponta para /v/{código}).
Revogar
POST /api/certificates/{código}/revoke com {"reason":"produto falsificado"} — permissão certificates:write. Registra a revogação na blockchain; a página pública passa a mostrar “Revogado” e a transferência é bloqueada. O motivo é interno (não é público). Desfazer uma revogação exige o admin do contrato (multisig), por desenho.
Transferência de titularidade
Fluxo público em duas etapas, para o revendedor/dono do produto:
POST /api/certificates/{código}/transfer/requestcom{"email":"…"}. A resposta é sempre a mesma, exista ou não o e-mail (não revela o dono). Se o e-mail bater, um código de 15 min é enviado por e-mail ao dono.POST …/transfer/confirmcom{"token","newName","newEmail"}. Grava um novo hash on-chain e acrescenta “Transferido” ao histórico. Cada código é de uso único; 5 erros bloqueiam o certificado por 15 min.
Webhooks de ERP e lojas
No painel, Conectar loja / ERP gera a URL /api/erp/webhook/{id} e a chave da integração. Cada unidade comprada vira um certificado; reenvios do mesmo pedido não duplicam.
| Plataforma | Autenticação | Quando emite |
|---|---|---|
| Shopify | HMAC-SHA256 (X-Shopify-Hmac-Sha256) com o segredo dos webhooks | Tópico orders/paid ou orders/create; itens sem SKU (frete) são ignorados |
| WooCommerce | HMAC-SHA256 (X-WC-Webhook-Signature), segredo = chave Remo | Pedido processing ou completed |
| Bling, Tiny, Omie, VTEX, ERP próprio | x-api-key ou ?key= | Payload padrão abaixo (via n8n/Zapier/Make ou TI da marca) |
Payload padrão:
POST /api/erp/webhook/{id} x-api-key: sk_live_…
{
"nf": "NF 000482", "order_id": "PED-1029",
"owner_name": "Maria Souza", "owner_email": "maria@exemplo.com",
"items": [ { "sku": "BLS-2291", "desc": "Bolsa Atelier", "quantity": 2 } ]
}
Resposta 201 com certificates: [{ code, txHash, duplicate, verifyUrl }]; 200 se o pedido já foi processado ou o evento é ignorado (ex.: pedido cancelado).
Bling/Tiny/Omie/VTEX enviam só um aviso com o ID do pedido; um conector nativo exige consultar a API deles com as credenciais da marca e ainda não existe. Hoje use o payload padrão.
Lojas parceiras
A marca autoriza cada loja no painel e entrega o código (LM-…) e a chave (rtk_…). A loja emite em /r/{marca} ou por API:
curl -X POST https://SEU-DOMINIO/api/certificates \
-H "x-retailer-code: LM-ABC123" -H "x-retailer-key: rtk_…" -H "x-brand-slug: atelier-marin" \
-H "Content-Type: application/json" -d '{ … }'
Lojas sem autorização podem solicitar a emissão em POST /api/public/brands/{marca}/requests; nada é emitido até a marca aprovar.
Widget e QR Code
<script src="https://SEU-DOMINIO/embed.js" data-remo-brand="sua-marca" async></script>
Opções: data-remo-mode="inline|button", data-remo-accent="#234F60", data-remo-label="…". Para posicionar, use <div data-remo-verify></div>. O widget roda em Shadow DOM (não interfere no CSS do seu site) e só chama a consulta pública. Uma página com ?remo=RM-… já verifica sozinha. Se seu site usa CSP, permita o host da Remo em script-src e connect-src.
Verificar sem confiar na Remo
O contrato RemoCertificate guarda, para cada certificado, o histórico de hashes — nunca dados pessoais. Qualquer pessoa que tenha os dados do certificado (a marca, ou o proprietário) confere por conta própria:
tokenId = uint256( keccak256( utf8(code) ) )
hash = keccak256( utf8( JSON.stringify({
code, brand, nf, sku, ownerName, ownerEmail, issuedAt // nesta ordem
}) ) )
No explorer da rede, em Read Contract, chame verify(tokenId, hash): retorna (valid, revoked). Ou use o verificador aberto do repositório:
node scripts/verify-certificate.js --rpc https://SEU-RPC --contract 0x… --file certificado.json
Ele imprime AUTÊNTICO, REVOGADO, ADULTERADO/DESATUALIZADO ou NÃO ENCONTRADO. Detalhes do modelo de confiança em docs/AUDIT.md.
Erros e limites
Erros têm o formato { "error": "mensagem" }.
| HTTP | Quando |
|---|---|
| 400 | Campo faltando, formato inválido ou texto grande demais |
| 401 / 403 | Credencial ausente/inválida / sem a permissão necessária |
| 404 | Certificado, marca ou integração inexistente (ou de outra marca) |
| 409 | Estado incompatível (ex.: já revogado) |
| 429 | Limite de requisições |
| Limite (por IP) | |
|---|---|
| Consulta pública | 90 / minuto |
| API autenticada | 600 / minuto |
| Webhooks | 600 / minuto |
| Transferência | 12 / hora |
| Login | 15 / 15 min |
| Solicitações públicas de lojas | 20 / hora |
Verificação de saúde: GET /api/health e GET /api/health/ready.