HystoreWiki

3 min readView as Markdown

Theme API

https://theme.hystore.com.br/v1 reads and changes a store's code theme. It is the API the hystore tool uses, open to your integrations and to CI. The full list of routes, with their formats, is in the OpenAPI.

1. Create a key

  1. In the dashboard, open Customization > Theme access > Create key.
  2. Give it a name (for example, Site deploy).
  3. Pick what it may do:
Scope Allows
theme:read read files, versions and media; check; preview links
theme:write change the draft, images and fonts; bring a version back
theme:publish-request ask to publish (approved in the dashboard)
  1. Pick how long it lasts (30 to 365 days) and, if you want, the IPs that may use it.
  2. Copy the key (hys_theme_...). It shows only once: keep it like a password, in a vault or in the CI secrets.

2. Test the key

Terminal
export HYSTORE_THEME_TOKEN=hys_theme_...
curl https://theme.hystore.com.br/v1/me -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"
JSON
{
  "storeId": "0f558e5e-...",
  "store": { "name": "My Store", "domain": "mystore.hystore.com.br" },
  "scopes": ["theme:read", "theme:write"],
  "credential": { "id": "...", "kind": "key" }
}

The store is always the key's store: no route takes a store id. A key never does more than the role of the person who created it allows, and revoking it in the dashboard cuts it at once.

Read the theme

Terminal
# Draft files, with sha256 and size, and the draft revision
curl https://theme.hystore.com.br/v1/theme/files -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"

# One file
curl "https://theme.hystore.com.br/v1/theme/file?path=sections/hero.liquid" -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"

# Every text file at once (with content and hash)
curl https://theme.hystore.com.br/v1/theme/export -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"

A file comes like this:

JSON
{ "path": "sections/hero.liquid", "content": "<section class=\"hero\">...", "sha256": "3f1c..." }

Write safely

Send back the sha256 you read as baseSha:

Terminal
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\":\"Title\"}]}{% endschema %}","baseSha":"3f1c..."}'
  • If the file changed since you read it, the answer is 409 with the current hash, and nothing is written. Read again, merge and write.
  • baseSha: null means the file must not exist yet (create without erasing someone else's).
  • Without baseSha, the write overwrites.

Several files at once, all or nothing (up to 100 changes):

Terminal
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\":\"Welcome\"}}","baseSha":"9a0b..."},{"path":"sections/old.liquid","content":null}],"expectedRevision":12}'

content: null deletes. expectedRevision (optional) refuses the write if another one got there first since you read.

A file that breaks a rule gets 422 with each problem:

JSON
{ "errors": [{ "code": "html_event_handler", "message": "Event attributes (onclick, onload...) are not allowed. Use data-hystore-* components.", "path": "sections/hero.liquid", "line": 3 }] }

Check, preview and publish

Terminal
# Checks the whole draft
curl -X POST https://theme.hystore.com.br/v1/theme/check -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"

# A 24-hour link that opens the draft on the store's address
curl -X POST https://theme.hystore.com.br/v1/theme/preview -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"

# Asks to publish (someone approves in the dashboard)
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":"Black Friday banner"}'

Versions

Terminal
# The last 30 published versions
curl https://theme.hystore.com.br/v1/theme/versions -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"

# A file as it was in version 3
curl "https://theme.hystore.com.br/v1/theme/versions/3/file?path=styles/theme.css" -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"

# Brings version 3 back to the draft (the live store does not change)
curl -X POST https://theme.hystore.com.br/v1/theme/versions/3/restore -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"

Images and fonts

Terminal
# Lists them, with each public address
curl https://theme.hystore.com.br/v1/theme/assets -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"

# Uploads (the body is the file; the type is read from the content)
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

# Deletes
curl -X DELETE "https://theme.hystore.com.br/v1/theme/asset?path=assets/banner.webp" -H "Authorization: Bearer $HYSTORE_THEME_TOKEN"

PNG, JPEG, WebP, GIF and AVIF images up to 3 MB; WOFF and WOFF2 fonts up to 1 MB. SVG is not accepted as a media file.

Node.js example

Changes a text of the theme and asks to publish. Works on Node.js 18 or newer, with no libraries.

update-title.mjsJavaScript
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. Reads the English texts file
const file = await call('GET', '/theme/file?path=locales/en.json')
const texts = JSON.parse(file.content)

// 2. Changes the title and writes, with the hash it read (409 if someone changed it first)
texts.home = { ...texts.home, title: 'Season 5 is here' }
await call('PUT', '/theme/file?path=locales/en.json', { content: JSON.stringify(texts, null, 2), baseSha: file.sha256 })

// 3. Asks to publish
await call('POST', '/theme/publish-requests', { note: 'Season 5 title' })
console.log('Done: approve it in Customization > Code theme.')
Terminal
HYSTORE_THEME_TOKEN=hys_theme_... node update-title.mjs

Routes

Route Scope
GET /v1/me any
GET /v1/theme, GET /v1/theme/files, GET /v1/theme/file?path= read
GET /v1/theme/export read
PUT and 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 and DELETE /v1/theme/asset?path= read, write
POST /v1/theme/publish-requests publish-request

Error answers

Every error has the same shape and never carries internal details:

JSON
{ "errors": [{ "code": "SCOPE_REQUIRED", "message": "This credential cannot do this (needs theme:write)." }] }
Status Means
400 malformed request (path, body)
401 no key, a wrong, expired or revoked key
403 the key lacks the scope, the IP is not on the list, the plan lacks the module, or the request came from a web page
404 the file or the version does not exist
409 the file or the draft changed since you read it
413 file or batch too big
422 the file breaks a theme rule (with the line)
429 too many calls; wait the seconds in Retry-After

Limits

  • 120 reads and 30 writes per minute per key; above that, 429 with Retry-After.
  • Files: 256 KB each, 400 files, 5 MB of text. Media: images 3 MB, fonts 1 MB, 100 files, 40 MB.