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

  1. Peça o acesso ao painel e crie uma chave de API em API e chaves.
  2. Faça um POST por venda em /api/certificates (ou conecte Shopify/WooCommerce sem código, em Conectar loja / ERP).
  3. 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:

CredencialFormatoUso
Chave de API da marcark_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 parceiraheaders x-retailer-code, x-retailer-key, x-brand-slugSomente 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.

CampoTipo
nftexto ≤ 60obrigatório — nota fiscal ou pedido
skutexto ≤ 60obrigatório — código do produto
desctexto ≤ 200opcional — descrição exibida no certificado
ownerNametexto ≤ 120obrigatório — proprietário atual
ownerEmaile-mailobrigatório — necessário para transferência de titularidade
accent#RRGGBBopcional — 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": "…" }
}
statusSignifica
authenticO registro confere com o último hash on-chain e não foi revogado.
revokedA marca revogou o certificado (falsificação, roubo, devolução).
tamperedOs 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:

  1. POST /api/certificates/{código}/transfer/request com {"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.
  2. POST …/transfer/confirm com {"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.

PlataformaAutenticaçãoQuando emite
ShopifyHMAC-SHA256 (X-Shopify-Hmac-Sha256) com o segredo dos webhooksTópico orders/paid ou orders/create; itens sem SKU (frete) são ignorados
WooCommerceHMAC-SHA256 (X-WC-Webhook-Signature), segredo = chave RemoPedido processing ou completed
Bling, Tiny, Omie, VTEX, ERP própriox-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" }.

HTTPQuando
400Campo faltando, formato inválido ou texto grande demais
401 / 403Credencial ausente/inválida / sem a permissão necessária
404Certificado, marca ou integração inexistente (ou de outra marca)
409Estado incompatível (ex.: já revogado)
429Limite de requisições
Limite (por IP)
Consulta pública90 / minuto
API autenticada600 / minuto
Webhooks600 / minuto
Transferência12 / hora
Login15 / 15 min
Solicitações públicas de lojas20 / hora

Verificação de saúde: GET /api/health e GET /api/health/ready.