Integração técnica avançada
A API de leitura do catálogo e a cópia diária para o Data Lake, para quem liga outro sistema ao AdCore.
Esta página é para quem liga outro sistema ao catálogo do AdCore: o integrador ou a equipe de dados do site. Ela traz a API de leitura, que entrega produtos, variantes, ativos do DAM, eventos e os arquivos da cópia diária do catálogo para o Data Lake do site. A cópia diária vem desligada em cada site; quando ligada, os arquivos de uma cópia pronta podem ser baixados por GET /api/v1/exports/latest. Só o envio direto ao bucket do site ainda é proposta. O fim da página explica o download e o padrão dos arquivos.
API de leitura
A API de leitura entrega a um sistema de fora o catálogo de um site do AdCore: produtos, variantes (SKUs), ativos do DAM e os eventos que mudam esse catálogo. Todas as rotas são GET, usam HTTPS e respondem em JSON. Os mesmos eventos também podem chegar por webhook, a pedido do site.
| Item | Valor |
|---|---|
| Endereço-base | https://www.adcore.com.br |
| Versão | /api/v1, no caminho de todas as rotas |
| Contrato | https://www.adcore.com.br/api/v1/openapi.json, em OpenAPI 3.1, aberto e sem chave |
| Autenticação | cabeçalho Authorization: Bearer <chave> |
| Limite | 120 chamadas por minuto por chave |
O contrato, em inglês, descreve cada campo e cada resposta, e é a referência quando houver dúvida. A pedido do site, a equipe AdCore envia o guia da API junto com a coleção do Postman (adcore-public-api.postman_collection.json), que traz as rotas prontas para testar. Esta seção traz o que o integrador precisa para fazer a primeira chamada e manter a integração de pé.
A chave de acesso
A chave é emitida pelo Administrador ou pelo Proprietário da organização (capítulo 3), em Configurações → API. Os outros papéis não veem esse item no menu. Quem abre o endereço sem um desses papéis vê "Sem permissão para ver as chaves da API".
A chave vale para um site só: o site ativo na hora da emissão. Ela lê apenas o catálogo desse site. Um produto, uma variante ou um ativo de outro site responde 404, como se não existisse.
Passo 1 de 4 · Emita a chave
Em Configurações → API, na seção Chave de leitura, clique em Emitir chave. O botão só fica ativo quando o site não tem chave ativa. Com uma chave ativa, o caminho é rotacionar (passo 4).
Passo 2 de 4 · Copie a chave na hora
A chave aparece uma única vez. A tela avisa:
Esta é a única vez que a chave aparece
Clique em Copiar e guarde a chave num cofre de senhas. Ao recarregar a página, ela some, e nem o suporte consegue recuperá-la. Se a chave se perder, rotacione ou revogue e emita outra.
A chave tem a forma adc_<id>.<segredo>: o prefixo adc_, 22 caracteres, um ponto e mais 43 caracteres. Use-a inteira.
Passo 3 de 4 · Configure e teste
Guarde a chave numa variável de ambiente. O read -s pede a chave sem mostrá-la na tela e sem gravá-la no histórico do terminal:
read -rs ADCORE_API_KEY && export ADCORE_API_KEY curl -H "Authorization: Bearer $ADCORE_API_KEY" \ "https://www.adcore.com.br/api/v1/products?limit=1"
Uma resposta 200 com o campo data confirma que a chave funciona. Um 403 com not_contracted também prova que a chave está certa: o site não contratou o PIM, e o teste deve usar uma rota do módulo contratado.
Passo 4 de 4 · Rotacione antes de vencer
A chave vale 90 dias a partir da emissão. Toda resposta 200 de uma rota de dados traz o vencimento no cabeçalho X-AdCore-Key-Expires-At. Nos últimos 14 dias, a resposta traz também X-AdCore-Key-Expires-In-Days, com os dias que faltam, e a tela API mostra este aviso:
Uma chave deste site vence em {dias} dias. Rotacione agora para trocar sem interromper a integração.
Clique em Rotacionar chave. A tela mostra a chave nova uma vez, como no passo 2. A anterior continua valendo por até 7 dias, nunca além do próprio vencimento. Troque a chave no sistema de fora dentro desse prazo. Até a anterior parar, a tela avisa em quantos dias ela para de funcionar. Enquanto a anterior ainda vale, a tela não deixa rotacionar de novo.
Se a chave vazar, use Revogar e depois Confirmar revogação. A chave para na hora: a próxima chamada com ela recebe 401, e uma chave revogada não volta. Em seguida, emita outra com Emitir chave.
A seção Uso e falhas, na mesma tela, mostra quando a chave foi usada e as recusas que recebeu, com o motivo. O uso aparece uma vez por hora por chave. Cada tipo de recusa aparece no máximo uma vez por hora, e a de chave vencida, uma vez por dia. Só entram as recusas de uma chave deste site.
A chave de leitura só lê. Um site com a escrita ligada, ou com uma chave de escrita ainda ativa, tem também a seção Chave de escrita. Essa chave não lê: numa rota da API de leitura, ela recebe 403. A API não tem rota de escrita: as rotas do contrato são de leitura. O contrato lista o código write_disabled, mas ele vale só para operações de escrita e nenhuma destas rotas o devolve.
Autenticação
Toda rota de dados leva a chave neste cabeçalho:
Authorization: Bearer adc_<id>.<segredo>
A palavra Bearer, um espaço e a chave inteira. Sem o cabeçalho, ou com a chave fora dessa forma, a resposta é 401. A chave é conferida antes dos parâmetros: um pedido sem chave recebe 401 mesmo com um parâmetro errado. Só a rota do contrato dispensa a chave.
As rotas
| Rota | O que devolve | Parâmetros | Módulo |
|---|---|---|---|
GET /api/v1/products | a lista de produtos, da alteração mais recente para a mais antiga | limit (1 a 100, padrão 50) e offset (padrão 0) | PIM |
GET /api/v1/products/{productId} | um produto, com os atributos, as variantes e os ativos | productId, no caminho | PIM |
GET /api/v1/variants?sku=… | as variantes dos SKUs pedidos, com até 20 ativos cada | sku, repetido, de 1 a 100 vezes | PIM |
GET /api/v1/variants/{variantId} | uma variante, com todos os ativos | variantId, no caminho | PIM |
GET /api/v1/assets/{assetId} | um ativo do DAM: imagem, vídeo, documento ou outro formato público | assetId, no caminho | DAM |
GET /api/v1/events | os eventos product.published e asset.changed, do mais antigo para o mais novo | cursor, limit (1 a 100, padrão 50) e type, repetível | PIM e DAM |
GET /api/v1/exports/latest | o manifesto de uma cópia completa, com links temporários para baixar os arquivos | format (jsonl, padrão; ndjson, sinônimo de jsonl; ou parquet) e dt (dia UTC, AAAA-MM-DD, opcional) | PIM, com Data Lake ligado no site |
GET /api/v1/openapi.json | o contrato da API | nenhum; dispensa a chave | — |
As rotas que trazem um único item devolvem o objeto dentro do campo data. Um id que não existe no site da chave responde 404.
Lista de produtos
curl -H "Authorization: Bearer $ADCORE_API_KEY" \ "https://www.adcore.com.br/api/v1/products?limit=50&offset=0"
Trecho da resposta, com valores ilustrativos:
{
"data": [
{
"id": "cm0exampleproduct0001",
"code": "PROD-1001",
"name": "Example product 110V",
"status": "active",
"familyId": "cm0examplefamily00001",
"sku": "SKU-1000",
"attributes": { "brand": "Example" }
}
],
"pagination": { "limit": 50, "offset": 0, "total": 1 }
}
Cada produto traz id (o productId), code (o código do produto, que não muda), name, status (draft, active ou archived), familyId, sku (o SKU da variante padrão) e attributes. Em attributes, cada chave é o código de um atributo e cada valor é um texto. Entram só os valores preenchidos no próprio produto, sem canal e sem idioma. A API não traz os valores preenchidos em cada variante nem os valores por canal ou por idioma: nenhuma rota de variante tem o campo attributes. Esses valores estão na tabela attribute_values da cópia diária, descrita adiante. A cópia é ligada a pedido do site, e os arquivos prontos são baixados por GET /api/v1/exports/latest.
Num produto com várias variantes, a variante padrão pode ser a que só agrupa as outras. Ela leva o código do produto, e então sku traz esse código. Os SKUs de venda estão em variants, nas variantes que não têm o código do produto.
A ordem segue a última alteração do registro do produto, e a resposta não traz essa data. Alterar só um atributo ou uma variante não muda a posição do produto na lista. Por isso, a lista não mostra o que mudou, e a API não tem filtro de «alterados desde».
A rota de eventos cobre dois casos: a publicação concluída num canal de venda e as mudanças feitas num ativo por uma ação no AdCore. Editar um atributo ou uma variante sem publicar não vira evento. Criar, arquivar ou remover um produto do catálogo também não. Para conferir o resto, leia os produtos de novo: a lista traz os atributos, e a rota do produto traz as variantes e os ativos.
Busca por SKU
Repita o parâmetro sku para buscar vários SKUs na mesma chamada, até 100:
curl -H "Authorization: Bearer $ADCORE_API_KEY" \ "https://www.adcore.com.br/api/v1/variants?sku=sku-1001&sku=SKU-9999"
Trecho da resposta, como o contrato traz:
{
"data": [
{
"id": "cm0examplevariant0001",
"productId": "cm0exampleproduct0001",
"sku": "SKU-1001",
"requestedSku": "sku-1001",
"name": "Example product 110V",
"isDefault": false,
"assets": [
{
"id": "cm0exampleasset000001",
"variantId": "cm0examplevariant0001",
"url": "https://www.adcore.com.br/api/media/example-token?v=3",
"altText": "Front view",
"tags": ["front"],
"isPrimary": true,
"format": "jpg",
"kind": "image",
"videoLink": null,
"documentRole": null,
"width": 1200,
"height": 1200,
"sizeBytes": 245760,
"version": 3,
"approvalStatus": "APPROVED",
"usageRights": { "license": null, "expiresAt": null },
"createdAt": "2026-10-01T12:00:00.000Z",
"updatedAt": "2026-10-20T09:30:00.000Z"
}
],
"assetsTotal": 1
}
],
"notFound": ["SKU-9999"]
}
O SKU foi enviado em minúsculas: requestedSku repete a grafia do pedido e sku traz o código do cadastro.
A resposta tem duas listas:
data: as variantes encontradas, na ordem dos SKUs enviados. Cada uma trazid(ovariantId),productId,sku(o código da variante no cadastro),requestedSku(o SKU como você enviou),name,isDefault,assetseassetsTotal;notFound: os SKUs sem variante no site, escritos como você enviou.
Maiúsculas e minúsculas são equivalentes na busca, com uma ressalva: a conversão feita por upper() no Postgres não trata todos os caracteres especiais como equivalentes. Um pedido com ß encontra um código guardado com ß ou SS, mas um código guardado como STRAßE001 pode não ser encontrado pelo pedido STRASSE001. Nesse caso, envie a grafia com ß. Mande cada SKU sem espaço nas pontas e sem vírgula. Um SKU repetido na chamada é respondido uma vez. Um SKU pode voltar em mais de uma variante: ligue cada resultado ao pedido por requestedSku e espere uma lista. Num produto com várias variantes, o código do produto não é um SKU e volta em notFound. Num produto com uma variante só, que leva o código do produto, a regra do código do site pode dizer em que atributo do produto fica o SKU. Nesse caso, a busca acha o produto pelo SKU desse atributo, e o código do produto volta em notFound. Na resposta, sku traz o código da variante e requestedSku, o SKU que você enviou. Sem essa indicação na regra, ou sem SKU nesse atributo, a busca acha o produto pelo código da variante.
Cada variante traz no máximo 20 ativos, em ordem decrescente do campo version. O campo assetsTotal diz quantos ela tem; a rota da variante traz todos. Em cada ativo, url é o endereço público do arquivo original, ou null quando o arquivo não pode ser entregue para fora, por exemplo, uma imagem ainda não aprovada ou com o direito de uso vencido, nos sites que usam essas travas.
O campo kind identifica o ativo: image, video, document ou other. Para um vídeo, videoLink pode trazer o link de reprodução; nos outros tipos, é null. Para um documento, documentRole traz o papel do documento, quando preenchido; nos outros tipos, é null. Modelos 3D e documentos de uso interno não entram na API. O exemplo acima é uma imagem.
Nas rotas do produto e da variante, assets também vem em ordem decrescente do campo version. Essa não é a ordem da galeria, e a API não traz a posição de cada foto na galeria. O campo isPrimary vem true na foto escolhida como principal na ficha do produto. Ele pode vir false em todas as fotos de uma variante e, mesmo assim, a loja recebe uma foto principal, pela regra do capítulo 18. Em tags, além das etiquetas livres, podem vir as etiquetas reservadas do AdCore: thumbnail, gallery, preferred_main_view e vtex-carousel. Elas marcam o papel da foto. Para usar só as etiquetas livres, descarte essas palavras.
Um produto, uma variante, um ativo
curl -H "Authorization: Bearer $ADCORE_API_KEY" \ "https://www.adcore.com.br/api/v1/products/cm0exampleproduct0001" curl -H "Authorization: Bearer $ADCORE_API_KEY" \ "https://www.adcore.com.br/api/v1/variants/cm0examplevariant0001" curl -H "Authorization: Bearer $ADCORE_API_KEY" \ "https://www.adcore.com.br/api/v1/assets/cm0exampleasset000001"
O produto traz os campos da lista, mais variants e assets. A variante traz os campos da busca por SKU, sem requestedSku nem assetsTotal, e com todos os ativos. O ativo traz os campos de assets mostrados acima. Os ids saem das respostas anteriores: o productId da busca por SKU, o id de cada variante e o id de cada ativo.
Eventos
curl -H "Authorization: Bearer $ADCORE_API_KEY" \ "https://www.adcore.com.br/api/v1/events?type=product.published&limit=100"
A resposta traz data, com os eventos, e nextCursor. Cada evento tem id, type, occurredAt e data. Em product.published, data traz productId, channelId, attempt e publishedAt. Em asset.changed, traz assetId e change, que diz o que mudou. Os valores são upload, url-ingest, dedupe (só quando o envio ligou o ativo a uma variante), replace, restore, delete, version-delete, alt-text, video-link, approval, tint-review, vtex-carousel, usage-rights, link, tags, ai-accept, ai-alt-batch, primary, reorder, preferred-view, collection-add, collection-remove, collection-move e kind. Só vira evento a mudança feita no AdCore e já concluída: o pedido para refazer as versões de um ativo não é evento. As mudanças automáticas ainda não viram evento: o ativo ligado a uma variante por uma importação do catálogo, copiado entre variantes irmãs, desfeito ao reverter uma importação ou refeito pelo processamento de mídia. Para conferir essas, leia de novo o produto ou o ativo. Trate um valor desconhecido de change como mudança e leia o ativo de novo.
Nos sites com a vista principal preferida ligada, uma pessoa marca na aba Mídia do produto a foto que deve ser a principal da variante. A foto marcada traz preferred_main_view em tags. O campo isPrimary não muda com essa marca. O evento preferred-view sai só para a foto marcada ou desmarcada. Quando a marca passa para outra foto, a anterior a perde sem evento próprio. Por isso, ao receber esse evento, leia de novo a variante da foto, pelo variantId que vem em GET /api/v1/assets/{assetId}.
Um evento entra na lista pelo menos dois minutos depois de registrado. O id de um evento não muda: use-o para descartar um evento repetido. Sem type, a rota traz os tipos dos módulos que o site contratou: product.published é do PIM e asset.changed é do DAM.
Paginação
Na lista de produtos, a página segue limit e offset. Para a próxima página, some limit ao offset. A leitura acaba quando offset chega a pagination.total. A lista pode mudar durante a leitura: um produto novo, ou um produto cujo registro foi alterado, vai para o início e empurra os seguintes uma posição. Por isso um produto pode vir repetido e outro pode ficar de fora. Numa leitura completa, descarte os repetidos pelo id e compare a quantidade de id diferentes com o pagination.total da última página. Se faltar produto, leia a lista de novo.
curl -H "Authorization: Bearer $ADCORE_API_KEY" \ "https://www.adcore.com.br/api/v1/products?limit=100&offset=100"
Nos eventos, a página segue um cursor. Mande o nextCursor da resposta como cursor na chamada seguinte, e a leitura continua logo depois do último evento recebido. Quando não há nada novo, data vem vazio e nextCursor volta igual. Guarde o último cursor e pergunte de novo mais tarde. Sem cursor, a leitura começa no evento mais antigo. Se ainda não há evento nenhum, nextCursor vem null: nesse caso chame de novo sem cursor.
curl -H "Authorization: Bearer $ADCORE_API_KEY" \ "https://www.adcore.com.br/api/v1/events?cursor=<nextCursor da resposta anterior>"
Da primeira carga à carga diária
A API não tem filtro de «alterados desde». A carga diária combina a rota de eventos com uma releitura periódica. O caminho completo pede o PIM e o DAM: um site só com o PIM não recebe asset.changed, e um site só com o DAM não tem as rotas de produto e de variante.
Passo 1 de 4 · Marque o ponto de partida
Chame GET /api/v1/events?limit=100 sem cursor. Siga o nextCursor até data vir vazio e guarde o último nextCursor. Se ele vier null, ainda não há evento: na leitura seguinte, chame de novo sem cursor. Faça este passo antes da carga completa: assim, nenhum evento registrado durante a carga se perde.
Passo 2 de 4 · Faça a carga completa
Percorra GET /api/v1/products?limit=100, como em «Paginação». A lista segue a última alteração: um produto alterado durante a leitura muda de posição e pode vir duas vezes ou ficar de fora. Ao terminar, compare os id distintos com o pagination.total da última página. Se faltar algum, percorra a lista de novo.
Para cada produto, chame GET /api/v1/products/{productId}, que traz as variantes e os ativos. É uma chamada por produto: a 120 por minuto, mil produtos levam cerca de nove minutos.
Passo 3 de 4 · Leia os eventos todo dia
Chame a rota com o cursor guardado, até data vir vazio. Grave os dados e só depois guarde o novo nextCursor. Descarte repetidos pelo id do evento.
product.published: releiaGET /api/v1/products/{productId}.asset.changed: releiaGET /api/v1/assets/{assetId}. O evento não diz o produto: ovariantIddo ativo leva à variante, e oproductIdda variante leva ao produto. UmvariantIdnulldiz que o ativo não está ligado a uma variante, e ele não aparece em nenhum produto. Um404diz que o ativo foi apagado. Para saber de qual produto ela saiu, guarde na sua base a qual produto cada ativo pertence.
O cursor não depende da chave e continua valendo depois de uma rotação. Use um cursor para cada combinação de type: o cursor de uma leitura filtrada pula os eventos que o filtro deixou de fora.
Passo 4 de 4 · Repita a carga completa de tempos em tempos
A releitura pega o que não vira evento: atributo ou variante editados sem publicar, produto novo ou arquivado e ativo ligado por importação. Um produto arquivado continua na lista, com status archived. Antes de tratar uma ausência como exclusão, chame GET /api/v1/products/{productId}: um 404 confirma. A frequência depende de quanto atraso a sua integração aceita.
Limites
| Limite | Valor |
|---|---|
| Chamadas por chave | 120 por minuto |
| Chamadas por endereço IP de origem | 600 por minuto, com chave ou sem |
| Itens por página | até 100; o padrão é 50 |
| SKUs por chamada | até 100 |
| Tamanho de um SKU | até 100 caracteres |
| Ativos por variante, na busca por SKU | até 20 |
| Tamanho da resposta da busca por SKU | até 2 MB (2.097.152 bytes) |
Toda resposta 200 de uma rota de dados traz a conta da chave em três cabeçalhos: X-RateLimit-Limit (o limite), X-RateLimit-Remaining (as chamadas que restam) e X-RateLimit-Reset (quando a contagem recomeça, em segundos Unix). Acima de qualquer um dos dois limites de chamadas, a resposta é 429, com o cabeçalho Retry-After em segundos.
Erros
Todo erro das rotas acima volta no mesmo formato, com a mensagem em inglês. Um caminho que não existe na API responde 404 em HTML. Nas rotas acima, um método que não seja GET responde 405.
{ "error": { "code": "unauthorized", "message": "Missing or invalid API key" } }
| Status | code | Quando | O que fazer |
|---|---|---|---|
400 | invalid_request | parâmetro inválido: limit fora de 1 a 100, offset inválido, nenhum sku ou mais de 100, sku fora da forma, cursor ou type inválidos, ou resposta da busca por SKU acima de 2 MB | corrija o parâmetro; na busca por SKU acima de 2 MB, divida os SKUs em mais chamadas |
401 | unauthorized | chave ausente, fora da forma, desconhecida, com segredo errado, vencida ou revogada, ou de um site inativo; a resposta é a mesma em todos os casos | confira o cabeçalho e a chave inteira; se a chave é do site, peça ao Administrador o motivo em Uso e falhas e, se ela venceu ou foi revogada, uma chave nova |
403 | not_contracted | a chave é válida, mas a rota serve um módulo (PIM ou DAM) fora do contrato do site; nos eventos, um type de módulo não contratado | não repita com a mesma chave; fale com a equipe AdCore sobre o contrato |
403 | insufficient_scope | uma chave de escrita numa rota de leitura | use a chave de leitura |
404 | not_found | o id não existe no site da chave, ou é de outro site | confira o id nas respostas da busca ou da lista |
429 | rate_limited | passou de 120 chamadas por minuto na chave, ou de 600 no endereço IP | espere os segundos de Retry-After e tente de novo |
500 | internal_error | erro inesperado no AdCore | tente de novo; se continuar, fale com o suporte e diga a hora exata da chamada |
Um 403 não se resolve tentando de novo: a resposta só muda quando a chave ou o contrato mudam.
Avisos por webhook
Os mesmos eventos da rota GET /api/v1/events podem chegar ao seu sistema por POST assinado. O recurso é ligado a pedido do site, pela equipe AdCore. Com ele ligado, o Administrador ou o Proprietário cadastra o endereço em Configurações → Webhooks e escolhe os eventos, product.published e asset.changed. A URL começa por https://, aponta para um endereço público e não leva usuário e senha. O segredo de assinatura aparece uma única vez: copie-o na hora. Gerar um segredo de assinatura novo troca o segredo. Nessa troca, o receptor deve aceitar os dois segredos por alguns minutos.
Cada entrega traz o evento no corpo, em JSON, e três cabeçalhos: X-AdCore-Event (o tipo), X-AdCore-Event-Id (o id do evento) e X-AdCore-Signature, na forma t=<segundos Unix>,v1=<hex>. O v1 é o HMAC-SHA256 do segredo sobre <t>.<corpo>. Calcule a assinatura sobre o corpo exatamente como chegou, sem reformatar o JSON. Recuse um t com mais de 300 segundos de diferença do seu relógio.
Responda com um status 2xx em até 10 segundos. Erro do servidor, 408, 429 ou falta de resposta geram nova tentativa. A espera começa em 1 minuto e dobra até 1 hora, com até 8 tentativas registradas por evento. Outro erro 4xx ou um redirecionamento não geram nova tentativa, e o redirecionamento não é seguido. Os eventos saem na ordem da lista: enquanto um evento espera nova tentativa, os seguintes esperam com ele. O mesmo evento pode chegar mais de uma vez: descarte as repetições por X-AdCore-Event-Id. A entrega começa em até uma hora depois do cadastro e só traz eventos a partir dele. A seção Recibos de entrega, na mesma tela, mostra as tentativas e o desfecho de cada uma. Enquanto a entrega não começa, a rota de eventos serve os mesmos eventos, com os mesmos id.
Cópia diária para o Data Lake
O AdCore gera, para o Data Lake do site, uma cópia completa diária do catálogo em arquivos. Com a cópia ligada para o site, sai todo dia uma cópia inteira do catálogo, em pastas por data, no formato dt=AAAA-MM-DD. Cada cópia leva um manifesto e um marcador de cópia pronta. A cópia vem desligada em cada site. Para baixar os arquivos de uma cópia pronta, use GET /api/v1/exports/latest, com a chave de leitura do site e o PIM contratado. As seções abaixo explicam o download e o padrão dos arquivos. Só o envio direto ao bucket do site ainda é proposta.
Baixar a cópia pela API
Use a mesma chave de leitura das outras rotas. O site precisa ter o PIM contratado e o Data Lake ligado pela equipe AdCore. A rota só lê cópias completas: chamá-la não inicia nem refaz a geração.
curl -H "Authorization: Bearer $ADCORE_API_KEY" \ "https://www.adcore.com.br/api/v1/exports/latest?format=jsonl"
Sem format, a rota usa jsonl; ndjson é outro nome para o mesmo formato. Para Parquet, use format=parquet: esse formato precisa ter sido gerado. Sem dt, a rota lê a última cópia completa do formato. Com dt=AAAA-MM-DD, lê a última cópia completa daquele dia UTC, dentro dos 30 dias UTC de retenção da API, incluindo hoje.
A resposta 200 traz o manifesto de download dentro de data, com snapshot_id, snapshot_at, partition_date, contract_version, previous_snapshot_id, include e tables. Cada entrada de tables[].files[] traz path, row_count, size_bytes, sha256 e url. Use url para baixar o arquivo: path é o nome dele no armazenamento, não um endereço de download. expires_in vale 900: cada link é válido por 15 minutos. Guarde os links em local privado; quem tiver um deles pode baixar o arquivo enquanto ele valer.
| Resposta | O que significa | O que fazer |
|---|---|---|
403 datalake_disabled | a cópia está desligada no site da chave | peça à equipe AdCore que confira a configuração do Data Lake do site |
404 export_not_ready | não há cópia completa do formato e do dia pedidos dentro da retenção, ou a cópia não passou pela conferência | confira formato e dia; aguarde uma cópia pronta antes de tentar de novo |
400 invalid_request | format ou dt inválido, repetido, ou outro parâmetro | use só os parâmetros da tabela de rotas |
As recusas de autenticação e de módulo contratado são as mesmas das outras rotas. Depois de baixar, confira tamanho, número de linhas e SHA-256 antes de carregar os dados, como em «Como consumir», adiante.
A carga se faz pelos arquivos listados no manifesto.
Pastas e nomes dos arquivos
Estes são os nomes no armazenamento do AdCore, que aparecem no campo path dos arquivos. O download pela API usa os links url e não exige acesso direto a essas pastas. Cada formato tem a sua raiz. Dentro dela, cada tabela tem a sua pasta, e a data vem depois do nome da tabela. O exemplo mostra uma cópia de 28/10/2026:
adcore/<id-do-site>/
├── jsonl/
│ ├── latest.json ponteiro da cópia mais recente
│ ├── _dates.json índice das datas guardadas
│ ├── dt=2026-10-28/
│ │ └── latest.json ponteiro da última cópia do dia
│ ├── _snapshots/
│ │ └── dt=2026-10-28/
│ │ └── <id-da-copia>/
│ │ ├── manifest.json
│ │ └── _SUCCESS
│ ├── products/
│ │ └── dt=2026-10-28/
│ │ └── <id-da-copia>/
│ │ ├── part-00000.jsonl
│ │ └── part-00001.jsonl
│ ├── variants/
│ │ └── dt=2026-10-28/<id-da-copia>/part-00000.jsonl
│ ├── ... uma pasta por tabela
│ └── deletions/
│ └── dt=2026-10-28/<id-da-copia>/part-00000.jsonl
└── parquet/
├── latest.json
├── _dates.json
├── _status.json como terminou o último Parquet
├── dt=2026-10-28/latest.json
├── _snapshots/dt=2026-10-28/<id-da-copia>/manifest.json
├── _snapshots/dt=2026-10-28/<id-da-copia>/_SUCCESS
└── products/dt=2026-10-28/<id-da-copia>/part-00000.parquet
- A data da pasta é a data UTC do instante da cópia.
- Cada cópia tem um identificador próprio. Um mesmo dia pode ter mais de uma cópia, por exemplo quando uma tentativa é refeita. Por isso a pasta da cópia fica dentro da pasta do dia.
- Uma tabela pode ter várias partes, numeradas com cinco dígitos a partir de
part-00000. - Quando o Parquet sai, as duas raízes trazem a mesma cópia, com o mesmo identificador e o mesmo instante.
O que cada tabela traz
A lista de tabelas e de colunas é fechada. Só sai o que está nela.
| Tabela | O que é | Campos principais |
|---|---|---|
products | os produtos | id, code, name, status (draft, active ou archived), family_id, created_at, updated_at |
variants | as variantes (SKUs) | id, product_id, code, name, is_default, created_at, updated_at |
attribute_values | os valores dos atributos, uma linha por valor | id, attribute_id, owner_level, product_id ou variant_id, channel_key, locale_key, scope_key, value_type e um campo de valor por tipo: value_string, value_number, value_boolean, value_datetime, value_json, value_ref_id |
attributes | o dicionário de atributos | id, code, slug, name, type, unit |
categories | as categorias | id, tree_id, parent_id, code, name |
product_categories | o vínculo entre produto e categoria | id, product_id, category_id, position |
channel_eligibility | a decisão de cada produto em cada canal | id, product_id, channel_id, enabled |
assets | as fotos e os demais ativos, sem o arquivo | id, variant_id, url, alt_text, is_primary, gallery_position, format, width, height, size_bytes |
external_ids | os códigos do item em outros sistemas, como a VTEX | id, entity_type, entity_id, channel_id, external_id |
deletions | o que sumiu desde a cópia anterior | table, row_id, deleted_on |
prices (opcional) | o preço de lista e o preço base | id, variant_id, channel_id, price_scope_key, currency, list_price, base_price, valid_from, valid_to |
Toda linha traz o identificador do site, na coluna tenant_id. As tabelas de dados trazem também as datas de criação e, quando há, de alteração.
idé o identificador estável do registro. O código comercial fica emcode.- A cópia é o estado do PIM, não só o que já foi publicado. Entram todos os produtos, inclusive rascunhos e arquivados, com variantes, valores, categorias e ativos.
- A habilitação de cada produto em cada canal está em
channel_eligibility. Um produto comenabledigual afalsecontinua na cópia. attribute_valuesvem em formato longo: uma linha por valor, não uma coluna por atributo. Só o campo do tipo indicado emvalue_typevem preenchido. Quando um valor é apagado da ficha, a linha continua, com todos os campos de valor nulos.- Um atributo do tipo referência traz só o identificador da referência, em
value_ref_id. - Em
assets,urlé o endereço do arquivo original, sem senha nem assinatura. Ele segue a mesma regra da API e é conferido no momento em que a cópia é gerada. Vem nulo quando o arquivo não pode ser entregue para fora naquele momento, por exemplo uma imagem ainda não aprovada ou com o direito de uso vencido, nos sites que usam essas travas. O endereço não garante o acesso depois da cópia. O arquivo da imagem não vai na cópia.
Tipos e valores
- Data e hora:
created_at,updated_at,value_datetime,valid_fromevalid_to. Dia:deleted_on. - Inteiro:
position,gallery_position,width,heightesize_bytes. - Booleano:
is_default,enabled,is_primaryevalue_boolean. - Decimal:
value_number,list_priceebase_price. - JSON:
value_json. No Parquet, ele vem como texto, com o JSON serializado. - As demais colunas são texto.
- Podem vir nulos:
family_id,unit,parent_id,url,alt_text,gallery_position,valid_from,valid_to, os seis campos de valor,product_idevariant_iddeattribute_valuesevariant_iddeassets. As outras colunas nunca vêm nulas. owner_level:productModel(o valor é do produto, emproduct_id) ouvariant(o valor é da variante, emvariant_id).value_type:string,number,boolean,datetime,jsonoureference.attributes.type:text,textarea,select,multiselect,boolean,date,number,metric,price,imageAssetLink,productLinkoureference.entity_type:productModel,variant,categoryouattribute.channel_key,locale_keyescope_key: o valor__default__indica o valor geral, sem canal, idioma ou escopo.__default__não é uma loja. Emprice_scope_key,__default__indica o preço sem escopo.family_id,tree_id,channel_idevalue_ref_idsão identificadores sem tabela de nomes na cópia. A cópia também não traz o vínculo entrechannel_keyechannel_id.- O
iddeproducts,variantseassets, e os camposproduct_idevariant_id, são os mesmos identificadores que a API devolve. - Em
variants, um produto com várias variantes pode trazer também a variante que só agrupa as outras, comcodeigual aocodedo produto.
Formato
- JSON Lines: um objeto JSON por linha, em UTF-8, sem BOM, com a extensão
.jsonl. O arquivo não é compactado. - Parquet: extensão
.parquet, com as mesmas colunas. As colunas saem compactadas com Snappy, que é a compressão padrão da biblioteca que grava o arquivo. Cada arquivo registra a versão do contrato e o nome da tabela nos metadados. - No JSON Lines, datas e horas vêm em ISO 8601 UTC, com
Z, e dias no formatoAAAA-MM-DD. No Parquet, data e hora são TIMESTAMP em milissegundos, em UTC, e o dia é DATE. - Decimais nunca viram número de ponto flutuante. No JSON Lines, vêm como texto. No Parquet,
list_priceebase_pricesãoDECIMAL(18,4), evalue_numbersegue como texto. - O JSON Lines é o formato garantido. O Parquet pode ser desligado pela equipe AdCore com
DATALAKE_EXPORT_PARQUET=off. Quando ligado, sai ao lado do JSON Lines, da mesma cópia. Se o Parquet de um dia não sair, o JSON Lines sai do mesmo jeito, e o manifesto do JSON Lines diz o resultado no campoformats.parquet:published,failedouskipped. Fora depublished,reasondiz o motivo.skippedvem sempre comdisabled_by_operator.failedvem comencode_failed,storage_failed,manifest_too_largeoudeadline. - No armazenamento,
parquet/_status.jsonregistra o resultado do último Parquet, eparquet/latest.jsonfica na cópia anterior quando a nova não sai. A API não entrega_status.jsonnem o campoformats.parquetdo manifesto gravado. Para saber se os downloads de JSON Lines e Parquet representam a mesma cópia, consulte a rota para cada formato e comparesnapshot_idesnapshot_at. Identificadores diferentes indicam cópias diferentes: não misture os arquivos delas. - A versão do contrato é a 1.1. Um leitor feito para a versão 1 aceita qualquer 1.x e ignora colunas e campos que não conhece. Uma versão 2 muda o contrato de forma incompatível.
Horário
Com a cópia ligada para o site, ela começa uma vez por dia, às 06:00 UTC, que são 03:00 em Brasília. Cada execução cobre um site só. Se uma tentativa falhar, ela é refeita em seguida, até um total de três tentativas. Se as três falharem, os ponteiros continuam na última cópia pronta, e a próxima chance é a execução do dia seguinte. Cada tentativa grava uma cópia nova, e a anterior não é tocada.
Manifesto e marcador
Cada cópia tem um manifest.json no armazenamento. Ele lista todas as tabelas, inclusive as vazias: dez sem preço, onze com preço. Uma tabela vazia vem com row_count igual a 0 e files vazio. O exemplo abaixo é do manifesto gravado; a resposta de download da API traz os campos necessários à carga e acrescenta url a cada arquivo e expires_in ao manifesto.
{
"contract_version": "1.1",
"tenant_id": "<id-do-site>",
"snapshot_id": "<id-da-copia>",
"snapshot_at": "2026-10-28T06:00:00.000Z",
"partition_date": "2026-10-28",
"format": "jsonl",
"mode": "full",
"include": { "prices": false },
"previous_snapshot_id": "<id-da-copia-anterior>",
"formats": { "parquet": { "status": "published", "reason": null } },
"tables": [
{
"name": "products",
"row_count": 1,
"files": [
{
"path": "adcore/<id-do-site>/jsonl/products/dt=2026-10-28/<id-da-copia>/part-00000.jsonl",
"row_count": 1,
"size_bytes": 240,
"sha256": "<64 caracteres hexadecimais>"
}
]
}
]
}
| Campo | O que diz |
|---|---|
contract_version | a versão do contrato |
tenant_id | o site |
snapshot_id | o identificador da cópia |
snapshot_at | o instante da cópia, em UTC |
partition_date | o dia da pasta dt= |
format | jsonl ou parquet |
mode | sempre full: a cópia é completa |
include.prices | se a cópia traz preços |
previous_snapshot_id | a cópia anterior usada para calcular as exclusões, ou nulo |
formats.parquet | só no manifesto do JSON Lines: como terminou o Parquet da mesma cópia |
tables[] | por tabela: name, row_count e files[] |
files[] | por arquivo: path, row_count, size_bytes e sha256 |
A soma das linhas dos arquivos é igual ao row_count da tabela. O sha256 vem em hexadecimal minúsculo, com 64 caracteres.
O marcador é o arquivo _SUCCESS, vazio, na mesma pasta do manifesto. A ordem de gravação é sempre a mesma:
- as partes de cada tabela;
- o
manifest.json; - o
_SUCCESS; - o ponteiro do dia;
- o ponteiro geral.
Sem _SUCCESS, a cópia não está pronta. Os ponteiros só mudam depois dele.
Cada ponteiro latest.json traz só: contract_version, tenant_id, format, snapshot_id, snapshot_at, manifest_path e manifest_sha256. O ponteiro do dia e o geral de uma mesma cópia têm o mesmo conteúdo.
Exclusões
O AdCore apaga o registro de verdade quando algo é excluído. Por isso a exclusão sai por comparação entre duas cópias.
- A tabela
deletionslista o que estava na cópia anterior e não está na atual.tablediz de qual tabela era o registro,row_iddiz qual era edeleted_ondiz o dia UTC em que a ausência foi notada. - A precisão é o dia. A data não é o instante exato da exclusão.
- Entra o que sumiu de qualquer tabela de dados: produtos, variantes, valores, atributos, categorias, vínculos, decisões por canal, ativos, códigos externos e, com o preço ligado, preços.
- Um produto arquivado não é exclusão. Ele continua em
products, comstatusigual aarchived. - Cada cópia traz só as exclusões desde a cópia anterior.
- A primeira cópia vem com
deletionsvazia eprevious_snapshot_idnulo. O mesmo vale para a primeira cópia depois de uma mudança de versão ou depois de ligar ou desligar o preço, ou quando a cópia anterior não pôde ser conferida. Nesses casos, substitua tudo pela cópia nova.
Reprocessar um dia
Para carregar de novo um dia, chame GET /api/v1/exports/latest?format=jsonl&dt=AAAA-MM-DD (ou troque jsonl por parquet) e repita os passos de consumo. A API lê o ponteiro daquele dia, em adcore/<id-do-site>/<formato>/dt=AAAA-MM-DD/latest.json. Vale para qualquer dia dentro da retenção da API.
- Cada cópia é completa. Carregar a cópia de um dia substitui o catálogo inteiro pelo daquele dia.
- Se a última cópia que você carregou não for a indicada em
previous_snapshot_id, ou se ele vier nulo, você não tem a lista de exclusões que falta. Carregue a cópia nova inteira: ela é a referência. - Um dia que já passou não é gerado de novo. Cada cópia guarda o catálogo do momento em que roda.
Retenção
No armazenamento, ficam guardados 30 dias UTC, contados até o dia da cópia mais recente, inclusive. Os dias mais antigos são apagados. A limpeza acontece a cada cópia diária. Com a cópia desligada, os dias já gravados não são apagados. O download pela API só entrega cópias dos últimos 30 dias UTC, incluindo hoje, e só enquanto o Data Lake estiver ligado no site.
Como consumir
Estes passos usam o download que já existe em GET /api/v1/exports/latest.
Passo 1 de 5 · Localize a cópia
Chame a rota com o formato que você usa e, para um dia específico, com dt. Se o snapshot_id de data for o da última cópia que você carregou, não há cópia nova nesse formato: consulte de novo mais tarde. A cópia começa às 06:00 UTC, e a hora de término não é fixa.
Passo 2 de 5 · Confira a resposta
A API confere o ponteiro, o manifesto e o marcador _SUCCESS antes de emitir os links. Uma resposta 404 export_not_ready não é uma cópia vazia: não carregue nada com essa resposta. Com 200, use o objeto data recebido até o fim da carga.
Passo 3 de 5 · Prepare o download
Confira se contract_version começa com 1.. Anote snapshot_id e previous_snapshot_id. Use os links de tables[].files[].url dentro dos 15 minutos. Se eles vencerem, peça outros à rota e confira se o snapshot_id continua o mesmo antes de retomar. Se mudou, comece a carga da cópia nova sem misturar arquivos da anterior.
Passo 4 de 5 · Confira o SHA-256
Baixe cada arquivo pelo url recebido. Não envie a chave da API ao endereço do arquivo: o próprio link temporário dá acesso a ele. Para cada arquivo, confira o SHA-256, o tamanho em bytes e o número de linhas:
sha256sum part-00000.jsonl wc -c < part-00000.jsonl wc -l < part-00000.jsonl
No macOS, use shasum -a 256 part-00000.jsonl. Num arquivo Parquet, wc -l não se aplica: leia o número de linhas nos metadados do próprio arquivo, com o leitor de Parquet que você usa, e compare com o row_count.
Use só os arquivos do manifesto. Nunca leia a pasta de um dia com um curinga: um mesmo dia pode ter partes de mais de uma cópia, e o curinga misturaria as duas.
Passo 5 de 5 · Carregue
Com tudo conferido, carregue a cópia nova no lugar da anterior, de uma vez, com todas as tabelas juntas. Não misture tabelas de cópias diferentes. Se a última cópia que você carregou for a de previous_snapshot_id, aplique também a tabela deletions ao seu histórico. Se não for, a cópia nova inteira é a referência.
O que entra e o que nunca entra
Entra o catálogo, nas tabelas da lista acima. Isso inclui produtos, variantes, valores de atributos, o dicionário de atributos, categorias e vínculos, a decisão por canal, os ativos, os códigos externos e as exclusões.
O preço entra só a pedido do site. A tabela prices traz só o preço de lista, o preço base, a moeda, o escopo e a vigência. Com o preço desligado, a tabela não existe: nem arquivo, nem entrada no manifesto, nem exclusão de preço. Quem liga o preço por site é a equipe AdCore.
Nunca entram:
- custo e margem;
- os metadados do ativo, inclusive o autor da foto que vem nos dados da câmera;
- identificadores de usuários e notas de auditoria;
- senhas, credenciais e endereços internos de armazenamento;
- campos internos de fila, de tentativa e de origem da importação;
- o arquivo das imagens: a cópia leva só a descrição e o endereço público;
- estoque.
Na plataforma hoje
O gerador da cópia diária faz parte da plataforma e vem desligado em cada site. Ligá-lo para um site pede uma preparação da equipe AdCore, a pedido do site. Os preços entram por um segundo ajuste, também a pedido. Com o gerador desligado, nada é gravado.
Proposta: envio direto ao bucket do site
O envio direto a um bucket do próprio site, na nuvem combinada com ele, ainda é proposta. Ele é diferente do download pela API, que já existe: hoje, um sistema do site usa a chave de leitura em GET /api/v1/exports/latest e baixa os arquivos pelos links de 15 minutos recebidos na resposta.