API do tema
https://theme.hystore.com.br/v1 lê e muda o tema em código de uma loja. É a
API que a ferramenta hystore usa, aberta para as suas integrações e para CI. A
lista completa de rotas, com os formatos, está na OpenAPI.
1. Crie uma chave
- No painel, abra Personalização > Acesso ao tema > Criar chave.
- Dê um nome (por exemplo, Deploy do site).
- Escolha o que ela pode fazer:
| Escopo | Permite |
|---|---|
theme:read |
ler arquivos, versões e mídia; conferir; links de prévia |
theme:write |
mudar o rascunho, imagens e fontes; trazer uma versão de volta |
theme:publish-request |
pedir publicação (aprovada no painel) |
- Escolha a validade (30 a 365 dias) e, se quiser, os IPs que podem usá-la.
- Copie a chave (
hys_theme_...). Ela aparece uma vez só: guarde como uma senha, num cofre ou nos segredos do CI.
2. Teste a chave
export HYSTORE_THEME_TOKEN=hys_theme_...
curl https://theme.hystore.com.br/v1/me -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
{
"storeId": "0f558e5e-...",
"store": { "name": "Minha Loja", "domain": "minhaloja.hystore.com.br" },
"scopes": ["theme:read", "theme:write"],
"credential": { "id": "...", "kind": "key" }
}
A loja é sempre a da chave: nenhuma rota recebe id de loja. Uma chave nunca faz mais do que o cargo de quem a criou permite, e revogar no painel corta na hora.
Ler o tema
# Arquivos do rascunho, com sha256 e tamanho, e a revisão do rascunho
curl https://theme.hystore.com.br/v1/theme/files -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Um arquivo
curl "https://theme.hystore.com.br/v1/theme/file?path=sections/hero.liquid" -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Todos os arquivos de texto de uma vez (com conteúdo e hash)
curl https://theme.hystore.com.br/v1/theme/export -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
Um arquivo vem assim:
{ "path": "sections/hero.liquid", "content": "<section class=\"hero\">...", "sha256": "3f1c..." }
Gravar com segurança
Mande de volta o sha256 que você leu como baseSha:
curl -X PUT "https://theme.hystore.com.br/v1/theme/file?path=sections/hero.liquid" \
-H "Authorization: Bearer $HYSTORE_THEME_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content":"<section class=\"hero\"><h1>{{ section.settings.title }}</h1></section>\n{% schema %}{\"name\":\"Hero\",\"settings\":[{\"id\":\"title\",\"type\":\"text\",\"label\":\"Título\"}]}{% endschema %}","baseSha":"3f1c..."}'
- Se o arquivo mudou desde a sua leitura, a resposta é
409com o hash atual, e nada é gravado. Leia de novo, junte e grave. baseSha: nullquer dizer que o arquivo ainda não pode existir (criar sem apagar o de alguém).- Sem
baseSha, a gravação passa por cima.
Vários arquivos de uma vez, todos ou nenhum (até 100 mudanças):
curl -X POST https://theme.hystore.com.br/v1/theme/files \
-H "Authorization: Bearer $HYSTORE_THEME_TOKEN" \
-H "Content-Type: application/json" \
-d '{"changes":[{"path":"locales/pt.json","content":"{\"home\":{\"title\":\"Bem-vindo\"}}","baseSha":"9a0b..."},{"path":"sections/old.liquid","content":null}],"expectedRevision":12}'
content: null apaga. expectedRevision (opcional) recusa a gravação se outra
passou na frente desde a sua leitura.
Um arquivo que quebra uma regra recebe 422 com cada problema:
{ "errors": [{ "code": "html_event_handler", "message": "Event attributes (onclick, onload...) are not allowed. Use data-hystore-* components.", "path": "sections/hero.liquid", "line": 3 }] }
Conferir, ver a prévia e publicar
# Confere o rascunho inteiro
curl -X POST https://theme.hystore.com.br/v1/theme/check -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Link de 24 horas que abre o rascunho no endereço da loja
curl -X POST https://theme.hystore.com.br/v1/theme/preview -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Pede a publicação (alguém aprova no painel)
curl -X POST https://theme.hystore.com.br/v1/theme/publish-requests \
-H "Authorization: Bearer $HYSTORE_THEME_TOKEN" \
-H "Content-Type: application/json" \
-d '{"note":"Banner da Black Friday"}'
Versões
# As últimas 30 versões publicadas
curl https://theme.hystore.com.br/v1/theme/versions -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Um arquivo como estava na versão 3
curl "https://theme.hystore.com.br/v1/theme/versions/3/file?path=styles/theme.css" -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Traz a versão 3 de volta para o rascunho (a loja no ar não muda)
curl -X POST https://theme.hystore.com.br/v1/theme/versions/3/restore -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
Imagens e fontes
# Lista, com o endereço público de cada uma
curl https://theme.hystore.com.br/v1/theme/assets -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Envia (o corpo é o arquivo; o tipo é lido do conteúdo)
curl -X PUT "https://theme.hystore.com.br/v1/theme/asset?path=assets/banner.webp" \
-H "Authorization: Bearer $HYSTORE_THEME_TOKEN" \
--data-binary @banner.webp
# Apaga
curl -X DELETE "https://theme.hystore.com.br/v1/theme/asset?path=assets/banner.webp" -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
Imagens PNG, JPEG, WebP, GIF e AVIF até 3 MB; fontes WOFF e WOFF2 até 1 MB. SVG não entra como arquivo de mídia.
Exemplo em Node.js
Troca um texto do tema e pede a publicação. Funciona no Node.js 18 ou mais novo, sem bibliotecas.
const API = 'https://theme.hystore.com.br/v1'
const headers = { Authorization: `Bearer ${process.env.HYSTORE_THEME_TOKEN}`, 'Content-Type': 'application/json' }
async function call(method, path, body) {
const res = await fetch(API + path, { method, headers, body: body && JSON.stringify(body) })
const data = await res.json()
if (!res.ok) throw new Error(`${res.status} ${JSON.stringify(data.errors ?? data)}`)
return data
}
// 1. Lê o arquivo de textos em português
const file = await call('GET', '/theme/file?path=locales/pt.json')
const texts = JSON.parse(file.content)
// 2. Muda o título e grava, com o hash lido (409 se alguém mudou antes)
texts.home = { ...texts.home, title: 'Temporada 5 chegou' }
await call('PUT', '/theme/file?path=locales/pt.json', { content: JSON.stringify(texts, null, 2), baseSha: file.sha256 })
// 3. Pede a publicação
await call('POST', '/theme/publish-requests', { note: 'Título da temporada 5' })
console.log('Pronto: aprove em Personalização > Tema em código.')
HYSTORE_THEME_TOKEN=hys_theme_... node atualizar-titulo.mjs
Rotas
| Rota | Escopo |
|---|---|
GET /v1/me |
qualquer |
GET /v1/theme, GET /v1/theme/files, GET /v1/theme/file?path= |
read |
GET /v1/theme/export |
read |
PUT e DELETE /v1/theme/file?path=, POST /v1/theme/files |
write |
POST /v1/theme/check |
read |
POST /v1/theme/preview |
read |
GET /v1/theme/versions, GET /v1/theme/versions/{n}/file?path= |
read |
POST /v1/theme/versions/{n}/restore |
write |
GET /v1/theme/assets, PUT e DELETE /v1/theme/asset?path= |
read, write |
POST /v1/theme/publish-requests |
publish-request |
Respostas de erro
Todo erro tem o mesmo formato e nunca traz detalhe interno:
{ "errors": [{ "code": "SCOPE_REQUIRED", "message": "This credential cannot do this (needs theme:write)." }] }
| Status | Quer dizer |
|---|---|
400 |
pedido mal formado (caminho, corpo) |
401 |
sem chave, chave errada, vencida ou revogada |
403 |
a chave não tem o escopo, o IP não está na lista, o plano não tem o módulo, ou o pedido veio de uma página web |
404 |
o arquivo ou a versão não existe |
409 |
o arquivo ou o rascunho mudou desde a sua leitura |
413 |
arquivo ou lote grande demais |
422 |
o arquivo quebra uma regra do tema (com a linha) |
429 |
muitas chamadas; espere os segundos de Retry-After |
Limites
- 120 leituras e 30 escritas por minuto por chave; acima disso,
429comRetry-After. - Arquivos: 256 KB cada, 400 arquivos, 5 MB de texto. Mídia: imagens 3 MB, fontes 1 MB, 100 arquivos, 40 MB.
