Manage themes, theme templates, and email templates
/admin/api/themes
List all themes with their active status and template override count.
/admin/api/themes
Create a new theme. Theme names must be unique.
/admin/api/themes/{id}
Get a single theme with its storefront and email template override lists.
/admin/api/themes/{id}
Rename a theme. The Default theme (id=1) cannot be renamed. Theme names must be unique.
/admin/api/themes/{id}
Delete a theme and all its template overrides. The Default theme (id=1) and the currently active theme cannot be deleted.
/admin/api/themes/{id}/activate
Set a theme as the active theme. Invalidates all cached templates so the new theme takes effect immediately.
/admin/api/themes/{id}/duplicate
Duplicate a theme and all its template overrides into a new theme with the given name.
/admin/api/themes/{id}/preview
Start previewing a theme. Sets a signed cookie so the merchant sees this theme on the storefront without affecting other visitors.
/admin/api/themes/preview
Stop previewing a theme. Clears the preview cookie.
/admin/api/themes/{id}/templates
List all storefront templates in a theme with their override status.
/admin/api/themes/{id}/templates/{name}
Get the content of a storefront template within a theme. Returns the custom override if one exists, otherwise the built-in default.
/admin/api/themes/{id}/templates/{name}
Save a storefront template override within a theme. Validates Tera syntax before saving.
/admin/api/themes/{id}/templates/{name}
Delete a storefront template override from a theme, reverting to the built-in default. Returns 404 if no custom override exists.
/admin/api/themes/{id}/email-templates
List all email templates in a theme with their override status.
/admin/api/themes/{id}/email-templates/{name}
Get the content of an email template within a theme. Returns the custom override if one exists, otherwise the built-in default.
/admin/api/themes/{id}/email-templates/{name}
Save an email template override within a theme. Validates Tera syntax. Email templates are standalone HTML and do not extend the storefront base layout. `subject` is required and overrides the email subject line for this theme/template; it must be a single line of 200 chars or fewer. For per-locale subject overrides, write translations under the key returned as `subject_translation_key` on the GET response (typically `email_subject_<name>`) via the translations API.
/admin/api/themes/{id}/email-templates/{name}
Delete an email template override from a theme, reverting to the built-in default. Returns 404 if no custom override exists.
/admin/api/themes
List all themes with their active status and template override count.
[
{
"id": 1,
"name": "Default",
"is_active": true,
"template_count": 2,
"created_at": "2025-11-01T00:00:00Z",
"updated_at": "2025-12-01T10:00:00Z"
},
{
"id": 2,
"name": "Holiday 2025",
"is_active": false,
"template_count": 5,
"created_at": "2025-12-10T08:00:00Z",
"updated_at": "2025-12-15T14:30:00Z"
}
]
curl "https://yourshop.zeroshop.io/admin/api/themes" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/themes",
headers={"Authorization": "Bearer zspat_..."}
)
themes = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes", {
headers: { "Authorization": "Bearer zspat_..." }
});
const themes = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
themes = JSON.parse(resp.body)
/admin/api/themes
Create a new theme. Theme names must be unique.
{
"name": "Holiday 2025"
}
{
"id": 2,
"name": "Holiday 2025"
}
curl -X POST "https://yourshop.zeroshop.io/admin/api/themes" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{"name": "Holiday 2025"}'
import requests
resp = requests.post(
"https://yourshop.zeroshop.io/admin/api/themes",
headers={"Authorization": "Bearer zspat_..."},
json={"name": "Holiday 2025"}
)
theme = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes", {
method: "POST",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({ name: "Holiday 2025" })
});
const theme = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer zspat_..."
req["Content-Type"] = "application/json"
req.body = { name: "Holiday 2025" }.to_json
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
theme = JSON.parse(resp.body)
/admin/api/themes/{id}
Get a single theme with its storefront and email template override lists.
{
"id": 1,
"name": "Default",
"is_active": true,
"created_at": "2025-11-01T00:00:00Z",
"updated_at": "2025-12-01T10:00:00Z",
"templates": [
{
"name": "base",
"has_custom": false,
"updated_at": null
},
{
"name": "home",
"has_custom": true,
"updated_at": "2025-12-01T10:00:00Z"
},
{
"name": "product",
"has_custom": false,
"updated_at": null
}
],
"email_templates": [
{
"name": "order_confirmation",
"has_custom": false,
"updated_at": null
},
{
"name": "shipping_notification",
"has_custom": true,
"updated_at": "2025-12-05T09:30:00Z"
}
]
}
curl "https://yourshop.zeroshop.io/admin/api/themes/1" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/themes/1",
headers={"Authorization": "Bearer zspat_..."}
)
theme = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/1", {
headers: { "Authorization": "Bearer zspat_..." }
});
const theme = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/1")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
theme = JSON.parse(resp.body)
/admin/api/themes/{id}
Rename a theme. The Default theme (id=1) cannot be renamed. Theme names must be unique.
{
"name": "Winter Sale"
}
curl -X PUT "https://yourshop.zeroshop.io/admin/api/themes/2" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{"name": "Winter Sale"}'
import requests
resp = requests.put(
"https://yourshop.zeroshop.io/admin/api/themes/2",
headers={"Authorization": "Bearer zspat_..."},
json={"name": "Winter Sale"}
)
assert resp.status_code == 204
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/2", {
method: "PUT",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({ name: "Winter Sale" })
});
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/2")
req = Net::HTTP::Put.new(uri)
req["Authorization"] = "Bearer zspat_..."
req["Content-Type"] = "application/json"
req.body = { name: "Winter Sale" }.to_json
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
/admin/api/themes/{id}
Delete a theme and all its template overrides. The Default theme (id=1) and the currently active theme cannot be deleted.
curl -X DELETE "https://yourshop.zeroshop.io/admin/api/themes/3" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.delete(
"https://yourshop.zeroshop.io/admin/api/themes/3",
headers={"Authorization": "Bearer zspat_..."}
)
assert resp.status_code == 204
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/3", {
method: "DELETE",
headers: { "Authorization": "Bearer zspat_..." }
});
require "net/http"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/3")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer zspat_..."
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
/admin/api/themes/{id}/activate
Set a theme as the active theme. Invalidates all cached templates so the new theme takes effect immediately.
curl -X POST "https://yourshop.zeroshop.io/admin/api/themes/2/activate" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.post(
"https://yourshop.zeroshop.io/admin/api/themes/2/activate",
headers={"Authorization": "Bearer zspat_..."}
)
assert resp.status_code == 204
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/2/activate", {
method: "POST",
headers: { "Authorization": "Bearer zspat_..." }
});
require "net/http"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/2/activate")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer zspat_..."
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
/admin/api/themes/{id}/duplicate
Duplicate a theme and all its template overrides into a new theme with the given name.
{
"name": "Holiday 2025 (copy)"
}
{
"id": 3,
"name": "Holiday 2025 (copy)"
}
curl -X POST "https://yourshop.zeroshop.io/admin/api/themes/2/duplicate" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{"name": "Holiday 2025 (copy)"}'
import requests
resp = requests.post(
"https://yourshop.zeroshop.io/admin/api/themes/2/duplicate",
headers={"Authorization": "Bearer zspat_..."},
json={"name": "Holiday 2025 (copy)"}
)
new_theme = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/2/duplicate", {
method: "POST",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({ name: "Holiday 2025 (copy)" })
});
const newTheme = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/2/duplicate")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer zspat_..."
req["Content-Type"] = "application/json"
req.body = { name: "Holiday 2025 (copy)" }.to_json
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
new_theme = JSON.parse(resp.body)
/admin/api/themes/{id}/preview
Start previewing a theme. Sets a signed cookie so the merchant sees this theme on the storefront without affecting other visitors.
{
"previewing_theme_id": 2
}
curl -X POST "https://yourshop.zeroshop.io/admin/api/themes/2/preview" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.post(
"https://yourshop.zeroshop.io/admin/api/themes/2/preview",
headers={"Authorization": "Bearer zspat_..."}
)
data = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/2/preview", {
method: "POST",
headers: { "Authorization": "Bearer zspat_..." }
});
const data = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/2/preview")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(resp.body)
/admin/api/themes/preview
Stop previewing a theme. Clears the preview cookie.
curl -X DELETE "https://yourshop.zeroshop.io/admin/api/themes/preview" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.delete(
"https://yourshop.zeroshop.io/admin/api/themes/preview",
headers={"Authorization": "Bearer zspat_..."}
)
assert resp.status_code == 204
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/preview", {
method: "DELETE",
headers: { "Authorization": "Bearer zspat_..." }
});
require "net/http"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/preview")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer zspat_..."
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
/admin/api/themes/{id}/templates
List all storefront templates in a theme with their override status.
[
{
"name": "base",
"has_custom": false,
"updated_at": null
},
{
"name": "home",
"has_custom": true,
"updated_at": "2025-12-01T10:00:00Z"
},
{
"name": "product",
"has_custom": false,
"updated_at": null
}
]
curl "https://yourshop.zeroshop.io/admin/api/themes/1/templates" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/themes/1/templates",
headers={"Authorization": "Bearer zspat_..."}
)
templates = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/1/templates", {
headers: { "Authorization": "Bearer zspat_..." }
});
const templates = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/1/templates")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
templates = JSON.parse(resp.body)
/admin/api/themes/{id}/templates/{name}
Get the content of a storefront template within a theme. Returns the custom override if one exists, otherwise the built-in default.
{
"name": "home",
"content": "<h1>{{ t.home_hero_title | default(value='Welcome') }}</h1>",
"is_custom": true
}
curl "https://yourshop.zeroshop.io/admin/api/themes/1/templates/home" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/themes/1/templates/home",
headers={"Authorization": "Bearer zspat_..."}
)
template = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/1/templates/home", {
headers: { "Authorization": "Bearer zspat_..." }
});
const template = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/1/templates/home")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
template = JSON.parse(resp.body)
/admin/api/themes/{id}/templates/{name}
Save a storefront template override within a theme. Validates Tera syntax before saving.
{
"content": "<h1>{{ t.home_hero_title | default(value='Welcome') }}</h1>"
}
curl -X PUT "https://yourshop.zeroshop.io/admin/api/themes/1/templates/home" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{"content": "<h1>Custom home page</h1>"}'
import requests
resp = requests.put(
"https://yourshop.zeroshop.io/admin/api/themes/1/templates/home",
headers={"Authorization": "Bearer zspat_..."},
json={"content": "<h1>Custom home page</h1>"}
)
assert resp.status_code == 204
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/1/templates/home", {
method: "PUT",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({ content: "<h1>Custom home page</h1>" })
});
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/1/templates/home")
req = Net::HTTP::Put.new(uri)
req["Authorization"] = "Bearer zspat_..."
req["Content-Type"] = "application/json"
req.body = { content: "<h1>Custom home page</h1>" }.to_json
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
/admin/api/themes/{id}/templates/{name}
Delete a storefront template override from a theme, reverting to the built-in default. Returns 404 if no custom override exists.
curl -X DELETE "https://yourshop.zeroshop.io/admin/api/themes/1/templates/home" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.delete(
"https://yourshop.zeroshop.io/admin/api/themes/1/templates/home",
headers={"Authorization": "Bearer zspat_..."}
)
assert resp.status_code == 204
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/1/templates/home", {
method: "DELETE",
headers: { "Authorization": "Bearer zspat_..." }
});
require "net/http"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/1/templates/home")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer zspat_..."
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
/admin/api/themes/{id}/email-templates
List all email templates in a theme with their override status.
[
{
"name": "order_confirmation",
"has_custom": false,
"updated_at": null
},
{
"name": "shipping_notification",
"has_custom": true,
"updated_at": "2025-12-05T09:30:00Z"
}
]
curl "https://yourshop.zeroshop.io/admin/api/themes/1/email-templates" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/themes/1/email-templates",
headers={"Authorization": "Bearer zspat_..."}
)
templates = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/1/email-templates", {
headers: { "Authorization": "Bearer zspat_..." }
});
const templates = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/1/email-templates")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
templates = JSON.parse(resp.body)
/admin/api/themes/{id}/email-templates/{name}
Get the content of an email template within a theme. Returns the custom override if one exists, otherwise the built-in default.
{
"name": "order_confirmation",
"content": "<html><body><h1>Thank you for your order!</h1></body></html>",
"is_custom": true
}
curl "https://yourshop.zeroshop.io/admin/api/themes/1/email-templates/order_confirmation" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.get(
"https://yourshop.zeroshop.io/admin/api/themes/1/email-templates/order_confirmation",
headers={"Authorization": "Bearer zspat_..."}
)
template = resp.json()
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/1/email-templates/order_confirmation", {
headers: { "Authorization": "Bearer zspat_..." }
});
const template = await resp.json();
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/1/email-templates/order_confirmation")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer zspat_..."
resp = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
template = JSON.parse(resp.body)
/admin/api/themes/{id}/email-templates/{name}
Save an email template override within a theme. Validates Tera syntax. Email templates are standalone HTML and do not extend the storefront base layout. `subject` is required and overrides the email subject line for this theme/template; it must be a single line of 200 chars or fewer. For per-locale subject overrides, write translations under the key returned as `subject_translation_key` on the GET response (typically `email_subject_<name>`) via the translations API.
{
"content": "<html><body><h1>Custom order email</h1></body></html>",
"subject": "Your order is confirmed"
}
curl -X PUT "https://yourshop.zeroshop.io/admin/api/themes/1/email-templates/order_confirmation" \
-H "Authorization: Bearer zspat_..." \
-H "Content-Type: application/json" \
-d '{"content": "<html><body><h1>Custom order email</h1></body></html>", "subject": "Your order is confirmed"}'
import requests
resp = requests.put(
"https://yourshop.zeroshop.io/admin/api/themes/1/email-templates/order_confirmation",
headers={"Authorization": "Bearer zspat_..."},
json={
"content": "<html><body><h1>Custom order email</h1></body></html>",
"subject": "Your order is confirmed",
}
)
assert resp.status_code == 204
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/1/email-templates/order_confirmation", {
method: "PUT",
headers: {
"Authorization": "Bearer zspat_...",
"Content-Type": "application/json"
},
body: JSON.stringify({
content: "<html><body><h1>Custom order email</h1></body></html>",
subject: "Your order is confirmed"
})
});
require "net/http"
require "json"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/1/email-templates/order_confirmation")
req = Net::HTTP::Put.new(uri)
req["Authorization"] = "Bearer zspat_..."
req["Content-Type"] = "application/json"
req.body = {
content: "<html><body><h1>Custom order email</h1></body></html>",
subject: "Your order is confirmed"
}.to_json
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
/admin/api/themes/{id}/email-templates/{name}
Delete an email template override from a theme, reverting to the built-in default. Returns 404 if no custom override exists.
curl -X DELETE "https://yourshop.zeroshop.io/admin/api/themes/1/email-templates/order_confirmation" \
-H "Authorization: Bearer zspat_..."
import requests
resp = requests.delete(
"https://yourshop.zeroshop.io/admin/api/themes/1/email-templates/order_confirmation",
headers={"Authorization": "Bearer zspat_..."}
)
assert resp.status_code == 204
const resp = await fetch("https://yourshop.zeroshop.io/admin/api/themes/1/email-templates/order_confirmation", {
method: "DELETE",
headers: { "Authorization": "Bearer zspat_..." }
});
require "net/http"
uri = URI("https://yourshop.zeroshop.io/admin/api/themes/1/email-templates/order_confirmation")
req = Net::HTTP::Delete.new(uri)
req["Authorization"] = "Bearer zspat_..."
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
We value your privacy
We use cookies for essential site functionality and, with your consent, analytics to understand how our platform is used. No personal data is shared with third parties. See our Privacy Policy for details.