API Admin › Recursos

Temas

Os temas da biblioteca da loja: criar, baixar e gravar os arquivos, enviar imagens e fontes, guardar versões, abrir uma prévia, conferir e publicar. São as rotas que o CLI de temas usa, e servem a qualquer automação de agência.

EscopoO que abre
read_themesler os temas, os arquivos, as versões, a conferência e o zip
write_themescriar, renomear e excluir temas, gravar arquivos, versões e prévias
publish_themespublicar um tema (só junto com write_themes; o painel o marca sensível)

A biblioteca tem no máximo 20 temas, e a vitrine mostra um deles, o publicado. Nada do que a API grava aparece na vitrine antes da publicação. Depois de publicar, a vitrine mostra a mudança em até 60 segundos.

O que a integração cria, grava e publica aparece no painel "pela integração", com o nome dela; o que o login do CLI pela conta grava fica no nome da pessoa.

O objeto tema

{
  "id": "5b0d6f1e-...",
  "name": "Tema da agência",
  "source": "custom",
  "platform_theme": "pilar",
  "published": false,
  "tree": "9f2c4e0b8a7d6c5e4f3a2b1c0d9e8f7a",
  "errors": 0,
  "development": false,
  "expires_at": null,
  "github": {
    "repository": "agencia/tema-da-loja",
    "branch": "main",
    "folder": "tema",
    "url": "https://github.com/agencia/tema-da-loja",
    "status": "active",
    "last_commit": {
      "sha": "4f1c2b9e0d8a7c6b5a4f3e2d1c0b9a8f7e6d5c4b",
      "message": "Acrescenta o selo"
    },
    "synced_at": "2026-10-03T18:04:00.000Z"
  },
  "pull_request": null,
  "created_at": "2026-10-03T18:00:00.000Z",
  "updated_at": "2026-10-03T18:05:00.000Z"
}
  • source: platform é um tema da plataforma, com o código só para leitura (a loja muda o conteúdo pelo painel); custom tem o código na loja, e os arquivos se gravam pela API.
  • platform_theme: o tema da plataforma de onde vem o código. No custom, de qual ele nasceu (null quando veio de um zip).
  • tree: o estado dos arquivos em trabalho, que muda a cada gravação (null no tema da plataforma).
  • errors: quantos erros a conferência do código achou. Com erro, o tema não é publicado.
  • development: é um tema de desenvolvimento, do CLI ou da prévia de um pull request (ver Temas de desenvolvimento). Fica fora da biblioteca e nunca é publicado.
  • expires_at: quando o tema de desenvolvimento vence (null nos outros). Cada gravação empurra a data.
  • github: o tema conectado a uma branch do GitHub (ver O tema no GitHub): o repositório (dono/nome), a branch, a pasta do tema no repositório (vazia é a raiz), o endereço do repositório, a situação e o último commit sincronizado. status é active ou paused (o App perdeu o acesso: foi desinstalado, ou o repositório ou a branch sumiu). null no tema sem conexão. O tema conectado tem o código só para leitura pela API (ver Os arquivos).
  • pull_request: no tema de desenvolvimento da prévia de um pull request, o número, o repositório e o endereço dele; null nos outros.

Os temas

GET /themes

A biblioteca inteira, o publicado primeiro: { "data": [tema...], "next_cursor": null }. Cabe numa página. Os temas de desenvolvimento ficam de fora; ?development=only lista só eles, e ?development=include, a biblioteca e eles.

GET /themes/{id}

Um tema.

POST /themes

Acrescenta um tema à biblioteca. Responde 201 com o tema.

{
  "source": "fork",
  "theme_id": "<id do Pilar da loja>",
  "name": "Tema da agência"
}
sourceO que cria
platformum tema da plataforma (platform_theme: pilar, editorial, vertice, aura, colheita ou estante), num estilo (style, opcional)
duplicatea cópia de um tema da biblioteca (theme_id), com o código e o conteúdo dele
forka cópia de um tema da plataforma da biblioteca (theme_id) com o código inteiro na loja, para gravar os arquivos

name é opcional (até 60 caracteres) e não se repete na loja; sem ele, a API escolhe um livre.

PATCH /themes/{id}

Renomeia: { "name": "Novo nome" }. Nome repetido: 409.

DELETE /themes/{id}

Exclui o tema (204). O publicado, ou o que tem publicação agendada no painel, não sai (409).

Os arquivos

O tema conectado a uma branch do GitHub (ver O tema no GitHub), e o tema da prévia de um pull request, têm o código só para leitura: a gravação de arquivos, o envio de assets, a troca pelo zip e a restauração de uma versão respondem 409 theme_connected. A leitura e a publicação continuam iguais. O campo github do tema diz se ele está conectado, e pull_request, se é a prévia de um pull request.

GET /themes/{id}/files

A lista dos arquivos, com o checksum de cada um (a base da gravação) e os problemas da conferência:

{
  "theme_id": "5b0d6f1e-...",
  "read_only": false,
  "tree": "9f2c4e0b8a7d6c5e4f3a2b1c0d9e8f7a",
  "files": [
    {
      "path": "layout/theme.liquid",
      "checksum": "1a2b...",
      "size": 2048,
      "binary": false,
      "url": null
    },
    {
      "path": "assets/logo.png",
      "checksum": "3c4d...",
      "size": 5120,
      "binary": true,
      "url": "https://..."
    }
  ],
  "problems": [
    {
      "code": "unknown_snippet",
      "file": "sections/banner.liquid",
      "line": 3,
      "column": 5,
      "end_line": 3,
      "end_column": 28,
      "message": "O snippet botao não existe em snippets/",
      "severity": "error"
    }
  ]
}

O checksum são 32 caracteres hexadecimais (os 128 primeiros bits do SHA-256 do conteúdo). Os arquivos de um tema da plataforma vêm com read_only: true.

Os problemas vêm do verificador de temas, o mesmo do editor do painel:

  • code: a regra, estável e em inglês (unknown_snippet, missing_translation...). É por ele que o seu programa decide; a lista das regras, com a explicação e o conserto de cada uma, está no guia do verificador.
  • severity: error impede publicar; warning só informa.
  • line e column começam em 1; end_line e end_column marcam o fim do trecho, quando se sabe. Problema do tema inteiro (um template que falta) vem com file e line nulos.
  • Os problemas guardados antes do verificador vêm com code, column, end_line e end_column nulos até o tema ser conferido de novo (o check e a publicação conferem).

GET /themes/{id}/files/content?path=...

O conteúdo de um arquivo: { "path", "checksum", "binary", "content", "url" }. O texto vem em content; imagem, fonte e vídeo vêm pelo url, na CDN.

PUT /themes/{id}/files

Grava, apaga e move arquivos de texto, tudo ou nada:

{
  "write": [
    { "path": "sections/banner.liquid", "content": "...", "base": "1a2b..." },
    { "path": "snippets/selo.liquid", "content": "...", "base": null }
  ],
  "delete": [{ "path": "snippets/velho.liquid", "base": "5e6f..." }],
  "move": [
    {
      "from": "snippets/a.liquid",
      "to": "snippets/b.liquid",
      "base": "7a8b..."
    }
  ]
}

base é o checksum que foi lido (null para arquivo novo). Se algum arquivo mudou desde a leitura (outra pessoa no editor do painel, outro processo), nada é gravado e a resposta é 409 file_conflict, com os conflitos e o checksum de agora (null: o arquivo não existe mais):

{
  "error": {
    "type": "conflict",
    "code": "file_conflict",
    "message": "Estes arquivos mudaram desde a leitura. Leia de novo e grave com o checksum de agora.",
    "request_id": "...",
    "conflicts": [
      { "path": "sections/banner.liquid", "current_checksum": "9d0e..." }
    ]
  }
}
  • O corpo tem no máximo 1 MB: mande os arquivos em partes. Acima disso, 413 payload_too_large.
  • Imagens, fontes e vídeos vão por POST /themes/{id}/assets.
  • Gravar num tema da plataforma é 409 theme_read_only: crie um fork.
  • O código com erro é gravado (a resposta traz os problems); só não é publicado.

A resposta é a lista dos arquivos, como em GET /themes/{id}/files.

POST /themes/{id}/assets

Envia arquivos para assets/, em multipart/form-data: até 20 no campo files, de até 10 MB cada, gravados em assets/<nome do arquivo>. CSS e JS também podem ir por aqui. O campo bases, opcional, é um JSON com os checksums lidos:

curl https://api.denaryx.com/admin/2026-10/themes/<id>/assets \
  -H "Authorization: Bearer dnxa_..." \
  -F "files=@logo.png" \
  -F 'bases={"assets/logo.png": "3c4d..."}'

Com as bases, o envio confere como a gravação (409 file_conflict); sem elas, o arquivo com o mesmo nome é substituído.

As versões

O código de um tema custom guarda versões: a cada publicação que mudou o código, a cada zip enviado, antes de restaurar e quando se pede.

GET /themes/{id}/versions

{ "data": [versão...], "next_cursor": null }, da mais nova à mais antiga:

{
  "id": "e1f2...",
  "number": 4,
  "tree": "9f2c...",
  "note": "Antes do lançamento",
  "reason": "manual",
  "created_at": "2026-10-03T18:10:00.000Z",
  "current": true
}

reason: publish, manual, import, fork, before_restore ou before_import. Ficam as 100 mais recentes, mais as que uma publicação cita. A versão que nasce de um push numa branch do GitHub conectada ao tema vem com reason: import e a nota abc1234: primeira linha do commit.

POST /themes/{id}/versions

Guarda o código de agora como versão, com uma nota opcional ({ "note": "..." }, até 200 caracteres). Responde 201 com a versão.

POST /themes/{id}/versions/{version_id}/restore

O código da versão volta a ser o trabalho do tema; o de antes vira uma versão (before_restore). O conteúdo da loja e a vitrine não mudam. Responde com a lista dos arquivos.

O zip

GET /themes/{id}/export

O zip do código de um tema custom, sem o conteúdo da loja (application/zip).

POST /themes/import

Um tema novo a partir de um zip, em multipart/form-data: o zip no campo file (até 50 MB e 2.000 arquivos) e o nome, opcional, em name. As pastas de um tema são layout, templates, sections, blocks, snippets, assets, config e locales; o zip pode ter tudo dentro de uma pasta. Responde 201, com o tema e, em ignored, cada arquivo que ficou de fora e o motivo:

{ "theme": { "id": "...", "source": "custom", ... }, "ignored": [] }

Com development=true, nasce um tema de desenvolvimento, e content_from (opcional) diz de qual tema da loja vem o conteúdo (ver abaixo).

POST /themes/{id}/import

Os arquivos do zip (campo file) substituem os do tema; o código de antes vira uma versão, e o conteúdo da loja fica. Responde 200, no mesmo formato.

A prévia

POST /themes/{id}/previews

Uma prévia do tema na vitrine, antes de publicar:

  • { "type": "temporary" }: vale 60 minutos e acompanha o trabalho (cada gravação aparece ao recarregar). É a do desenvolvimento.
  • { "type": "shared", "name": "Para o cliente", "expires_in_days": 7, "frozen": true }: o link para mandar ao cliente, com validade de 1, 7 ou 30 dias. Congelado (frozen), mostra para sempre o código e o conteúdo de agora; senão, acompanha o trabalho. Aparece no painel, em Temas, onde se revoga.

Responde 201:

{
  "url": "https://minhaloja.denaryx.store/previa?token=...",
  "expires_at": "2026-10-10T18:00:00.000Z",
  "shared": {
    "id": "...",
    "name": "Para o cliente",
    "frozen_at": "2026-10-03T18:00:00.000Z",
    "created_at": "2026-10-03T18:00:00.000Z"
  }
}

O url leva o token e aparece só nesta resposta. A loja tem no máximo 20 links compartilhados ativos.

Conferir e publicar

GET /themes/{id}/check

O que o painel mostra em "Antes de publicar":

{
  "theme": { ... },
  "can_publish": true,
  "problems": [],
  "content_impact": [{ "where": "Ajustes gerais", "change": "O ajuste \"cor_destaque\" não existe mais no tema" }],
  "missing_templates": [{ "template": "product", "name": "presente", "count": 3 }],
  "live": { "theme_name": "Pilar", "published_at": "2026-09-30T12:00:00.000Z" }
}
  • can_publish: o token tem publish_themes e o código não tem erro.
  • content_impact: o conteúdo da loja que o código deste tema deixa de ler (a seção, o bloco ou o ajuste que não existe mais, ou o valor que volta ao padrão).
  • missing_templates: os modelos de página que produtos, categorias e páginas usam e o tema não tem; com ele no ar, essas páginas usam o modelo padrão.
  • live: o que a vitrine mostra agora (null se a loja nunca publicou).

POST /themes/{id}/publish

Pede o escopo publish_themes, para qualquer tema (o da plataforma também troca o que o comprador vê). O tema vai para a vitrine com o código e o conteúdo de agora, e o painel guarda a publicação para a loja voltar a ela. Corpo opcional: { "note": "Lançamento" } (até 200 caracteres). Responde com o tema.

Código com erro não vai ao ar:

{
  "error": {
    "type": "invalid_request",
    "code": "theme_has_errors",
    "message": "O código do tema tem 1 erro. Corrija antes de publicar.",
    "request_id": "...",
    "problems": [
      {
        "code": "unknown_snippet",
        "file": "sections/banner.liquid",
        "line": 3,
        "column": 5,
        "end_line": 3,
        "end_column": 28,
        "message": "O snippet botao não existe em snippets/",
        "severity": "error"
      }
    ]
  }
}

O tema de desenvolvimento nunca é publicado: 409 development_theme.

Agendar a publicação e voltar a uma publicação anterior ficam no painel nesta versão.

Temas de desenvolvimento

O tema de desenvolvimento é o de uma pessoa (ou de uma integração) trabalhando no código, como o denaryx tema dev faz (Desenvolver com o tema dev): cada um no seu, sem mexer no tema que o cliente está aprovando nem na vitrine.

  • Nasce por POST /themes/import com development=true: o código é o do zip, e o conteúdo (as seções e os ajustes montados no editor do painel) é a cópia do conteúdo do tema content_from, lido pelo código novo. Sem content_from, o padrão do tema enviado. O nome é opcional e, se já existir, ganha um número.
  • Não conta nos 20 da biblioteca: são até 5 por integração (ou pessoa) e 30 por loja; acima disso, 409 development_theme_limit.
  • Grava-se como qualquer tema custom (PUT .../files, POST .../assets, a prévia), e cada gravação empurra o expires_at para 7 dias depois.
  • Não é publicado nem agendado (409 development_theme).
  • Vence 7 dias depois da última gravação e sai na limpeza diária, com as prévias e as versões dele.
  • Não aparece no GET /themes sem o ?development. O painel o mostra na página Temas, em "Em desenvolvimento".
  • A prévia de um pull request também é um tema de desenvolvimento: o App da Denaryx no GitHub cria um para cada pull request aberto para a branch de um tema conectado (A prévia por pull request). Ele vem com o pull_request, o código muda só pelo push no pull request (a gravação pela API responde 409 theme_connected), conta nos 30 da loja e não nos 5 de cada um (até 10 por conexão), e o link dele conta nos 20 links da loja.

O ciclo do CLI

O CLI de temas (denaryx) faz este ciclo por você, com o verificador sem rede, as bases guardadas e os envios em partes. Para uma automação própria:

  1. GET /token: confere a integração e os escopos.
  2. GET /themes e, para começar, POST /themes com source: fork (ou POST /themes/import com o zip da agência).
  3. GET /themes/{id}/files e GET .../files/content?path= para baixar; guarde o checksum de cada arquivo.
  4. PUT /themes/{id}/files com as bases, em partes de até 1 MB; POST .../assets para os binários. Num 409 file_conflict, leia de novo os arquivos dos conflitos e decida.
  5. POST /themes/{id}/previews com temporary para ver na vitrine. Para cada pessoa trabalhar sem atropelar as outras, um tema de desenvolvimento (POST /themes/import com development=true).
  6. GET /themes/{id}/check e POST /themes/{id}/publish.

Mande Idempotency-Key nas escritas (Escrita repetível): numa queda de rede, repetir a chamada não grava duas vezes. Vale também para os envios de arquivos.