API del tema
https://theme.hystore.com.br/v1 lee y cambia el tema en código de una tienda.
Es la API que usa la herramienta hystore, abierta para tus integraciones y
para CI. La lista completa de rutas, con sus formatos, está en la
OpenAPI.
1. Crea una llave
- En el panel, abre Personalización > Acceso al tema > Crear llave.
- Dale un nombre (por ejemplo, Deploy del sitio).
- Elige lo que puede hacer:
| Alcance | Permite |
|---|---|
theme:read |
leer archivos, versiones y medios; revisar; enlaces de vista previa |
theme:write |
cambiar el borrador, imágenes y fuentes; traer una versión de vuelta |
theme:publish-request |
pedir publicación (se aprueba en el panel) |
- Elige cuánto dura (30 a 365 días) y, si quieres, las IP que pueden usarla.
- Copia la llave (
hys_theme_...). Aparece una sola vez: guárdala como una contraseña, en un baúl o en los secretos del CI.
2. Prueba la llave
export HYSTORE_THEME_TOKEN=hys_theme_...
curl https://theme.hystore.com.br/v1/me -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
{
"storeId": "0f558e5e-...",
"store": { "name": "Mi Tienda", "domain": "mitienda.hystore.com.br" },
"scopes": ["theme:read", "theme:write"],
"credential": { "id": "...", "kind": "key" }
}
La tienda es siempre la de la llave: ninguna ruta recibe id de tienda. Una llave nunca hace más de lo que permite el rol de quien la creó, y revocarla en el panel la corta al instante.
Leer el tema
# Archivos del borrador, con sha256 y tamaño, y la revisión del borrador
curl https://theme.hystore.com.br/v1/theme/files -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Un archivo
curl "https://theme.hystore.com.br/v1/theme/file?path=sections/hero.liquid" -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Todos los archivos de texto de una vez (con contenido y hash)
curl https://theme.hystore.com.br/v1/theme/export -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
Un archivo viene así:
{ "path": "sections/hero.liquid", "content": "<section class=\"hero\">...", "sha256": "3f1c..." }
Escribir con seguridad
Devuelve el sha256 que leíste 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..."}'
- Si el archivo cambió desde que lo leíste, la respuesta es
409con el hash actual, y no se escribe nada. Lee de nuevo, junta y escribe. baseSha: nullquiere decir que el archivo todavía no puede existir (crear sin borrar el de otra persona).- Sin
baseSha, la escritura pisa lo que haya.
Varios archivos de una vez, todos o ninguno (hasta 100 cambios):
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\":\"Bienvenido\"}}","baseSha":"9a0b..."},{"path":"sections/old.liquid","content":null}],"expectedRevision":12}'
content: null borra. expectedRevision (opcional) rechaza la escritura si
otra pasó antes desde tu lectura.
Un archivo que rompe una regla recibe 422 con 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 }] }
Revisar, ver la vista previa y publicar
# Revisa el borrador entero
curl -X POST https://theme.hystore.com.br/v1/theme/check -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Enlace de 24 horas que abre el borrador en la dirección de la tienda
curl -X POST https://theme.hystore.com.br/v1/theme/preview -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Pide la publicación (alguien aprueba en el panel)
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 de Black Friday"}'
Versiones
# Las últimas 30 versiones publicadas
curl https://theme.hystore.com.br/v1/theme/versions -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Un archivo como estaba en la versión 3
curl "https://theme.hystore.com.br/v1/theme/versions/3/file?path=styles/theme.css" -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Trae la versión 3 de vuelta al borrador (la tienda en el aire no cambia)
curl -X POST https://theme.hystore.com.br/v1/theme/versions/3/restore -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
Imágenes y fuentes
# Las lista, con la dirección pública de cada una
curl https://theme.hystore.com.br/v1/theme/assets -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
# Envía (el cuerpo es el archivo; el tipo se lee del contenido)
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
# Borra
curl -X DELETE "https://theme.hystore.com.br/v1/theme/asset?path=assets/banner.webp" -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
Imágenes PNG, JPEG, WebP, GIF y AVIF hasta 3 MB; fuentes WOFF y WOFF2 hasta 1 MB. SVG no entra como archivo de medios.
Ejemplo en Node.js
Cambia un texto del tema y pide la publicación. Funciona en Node.js 18 o más nuevo, sin 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. Lee el archivo de textos en español
const file = await call('GET', '/theme/file?path=locales/es.json')
const texts = JSON.parse(file.content)
// 2. Cambia el título y escribe, con el hash leído (409 si alguien lo cambió antes)
texts.home = { ...texts.home, title: 'Llegó la temporada 5' }
await call('PUT', '/theme/file?path=locales/es.json', { content: JSON.stringify(texts, null, 2), baseSha: file.sha256 })
// 3. Pide la publicación
await call('POST', '/theme/publish-requests', { note: 'Título de la temporada 5' })
console.log('Listo: apruébalo en Personalización > Tema en código.')
HYSTORE_THEME_TOKEN=hys_theme_... node actualizar-titulo.mjs
Rutas
| Ruta | Alcance |
|---|---|
GET /v1/me |
cualquiera |
GET /v1/theme, GET /v1/theme/files, GET /v1/theme/file?path= |
read |
GET /v1/theme/export |
read |
PUT y 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 y DELETE /v1/theme/asset?path= |
read, write |
POST /v1/theme/publish-requests |
publish-request |
Respuestas de error
Todo error tiene el mismo formato y nunca trae detalles internos:
{ "errors": [{ "code": "SCOPE_REQUIRED", "message": "This credential cannot do this (needs theme:write)." }] }
| Estado | Quiere decir |
|---|---|
400 |
pedido mal formado (ruta, cuerpo) |
401 |
sin llave, llave equivocada, vencida o revocada |
403 |
la llave no tiene el alcance, la IP no está en la lista, el plan no tiene el módulo, o el pedido vino de una página web |
404 |
el archivo o la versión no existe |
409 |
el archivo o el borrador cambió desde tu lectura |
413 |
archivo o lote demasiado grande |
422 |
el archivo rompe una regla del tema (con la línea) |
429 |
demasiadas llamadas; espera los segundos de Retry-After |
Límites
- 120 lecturas y 30 escrituras por minuto por llave; arriba de eso,
429conRetry-After. - Archivos: 256 KB cada uno, 400 archivos, 5 MB de texto. Medios: imágenes 3 MB, fuentes 1 MB, 100 archivos, 40 MB.
