Auth: X-Agent-Key: sq_agt_* (or a dashboard user session with X-Store-ID). Scopes: read_online_store_navigation, write_online_store_navigation (menus); read_content, write_content (pages, blogs, articles). Every resource is scoped to the key’s store — an id from another store answers 404. Mutations are audited and honour Idempotency-Key. Timestamps are RFC 3339 UTC; unset values are null. A change reaches the live storefront’s data files within a short while of the response, with no theme publish. Merchant guides: Navigation, Pages, Blog posts.

Responses and errors

Handles

A handle is the URL segment and the key a theme setting stores. Lowercase letters, digits and single hyphens, up to 255 characters. Omit handle on create and it is generated from the title ("About Our Brand" → about-our-brand), de-duplicated with -2, -3, …. A generated page handle that would collide with a built-in storefront page also gets a suffix ("About" → about-2). An explicit handle is never rewritten — a collision is 409. Renaming a resource never changes its handle; only an explicit handle update does. Scopes: read_online_store_navigation · write_online_store_navigation.
  • GET /online_store/menus — summaries, default menus first, then by title. Every store has main-menu and footer; they are created on first read if missing.
  • POST /online_store/menus — { "title", "handle"?, "items": MenuItemInput[] } → 201 data.menu
  • GET /online_store/menus/{id} → data.menu
  • PUT /online_store/menus/{id} — { "title", "handle"?, "items" }. Replaces the whole item tree in one transaction. Item ids you send back are kept; items without one get a new id.
  • DELETE /online_store/menus/{id} → data: { "deleted_id" }
MenuItemInput: { "id"?, "title", "type", "resource_id"?, "url"?, "items"? }. Menu: { id, handle, title, is_default, items: MenuItem[], created_at, updated_at }. MenuItem:
url is the storefront address the link resolves to now ("" when it resolves to nothing); raw_url is the stored address of an http link. resource_missing: true means the linked resource was deleted or is not on the storefront — the link is kept in the menu but left out of the storefront.

Item types

Validation: title 1–255 characters; nesting at most 3 levels; at most 250 items per menu, counting nested ones; a resource link’s resource_id must exist in the store when saved (an unpublished page is accepted and reported resource_missing). An http url must be an absolute http(s):// URL, a path starting with / (not //), or a mailto: / tel: link — empty, #…, javascript: and data: are rejected. The URL is derived from the resource’s current handle, so renaming a collection or page never strands a menu link on an old address. On the storefront, a link that no longer resolves is omitted together with its nested links.

Pages

Scopes: read_content · write_content.
  • GET /online_store/pages?query=&status=published|hidden|all&limit=&cursor= — data: { "pages": PageSummary[], "next_cursor" }, most recently updated first. query matches title and handle; limit 1–100, default 50.
  • POST /online_store/pages — PageInput → 201 data.page
  • GET /online_store/pages/{id} → data.page
  • PATCH /online_store/pages/{id} — any subset of PageInput → data.page
  • DELETE /online_store/pages/{id} → data: { "deleted_id" }
PageInput: { "title", "handle"?, "body_html"?, "published"?, "published_at"?, "template_suffix"?, "seo_title"?, "seo_description"? }
PageSummary omits body_html, template_suffix and the SEO fields. Publishing: "published": true without published_at publishes now; published_at may be in the past but not the future (there is no scheduled publishing). "published": false hides the page. seo_description is at most 512 characters; body_html at most 1 MiB.

Blogs

Scopes: read_content · write_content.
  • GET /online_store/blogs → data.blogs, by title
  • POST /online_store/blogs — { "title", "handle"?, "template_suffix"?, "seo_title"?, "seo_description"? } → 201 data.blog
  • GET /online_store/blogs/{id} · PATCH /online_store/blogs/{id}
  • DELETE /online_store/blogs/{id} → data: { "deleted_id", "articles_deleted" } — deletes the blog’s articles

Articles

Scopes: read_content · write_content. One list across every blog.
  • GET /online_store/articles?blog_id=&query=&status=published|hidden|all&limit=&cursor= — data: { "articles": ArticleSummary[], "next_cursor" }, most recently updated first
  • POST /online_store/articles — ArticleInput (blog_id required) → 201 data.article
  • GET /online_store/articles/{id} · PATCH /online_store/articles/{id} · DELETE /online_store/articles/{id}
ArticleInput: { "blog_id", "title", "handle"?, "author"?, "body_html"?, "summary_html"?, "image_url"?, "image_alt"?, "tags"?, "published"?, "published_at"?, "template_suffix"?, "seo_title"?, "seo_description"? }
ArticleSummary is the object above without body_html, summary_html, image_alt, tags, template_suffix, the SEO fields and created_at.
  • Handles are unique per blog. Changing blog_id re-checks the handle in the target blog (409 HANDLE_TAKEN).
  • tags are trimmed and de-duplicated case-insensitively; each at most 255 characters, at most 250.
  • summary_html at most 64 KiB; body_html at most 1 MiB. Publishing follows the same rules as pages.

HTML sanitization

body_html and summary_html are sanitized on write. Kept: headings, paragraphs, lists, links (with target / rel), images (src, alt, width, height, loading), tables, figure / figcaption, quotes, class attributes, and iframe embeds from https://www.youtube.com/embed/, https://www.youtube-nocookie.com/embed/ and https://player.vimeo.com/video/. Removed: scripts, style blocks, event-handler attributes and any other embed. The response carries the stored, sanitized HTML. GET /online_store/link_resources?type=&query=&limit= · scope read_online_store_navigation. type is product, collection, page, blog, article or policy; limit 1–50, default 20.
Products are the store’s active products; collections are published collections; pages include hidden ones (subtitle: "Hidden"); an article’s subtitle is its blog’s title.

Storefront data

GET /online_store/storefront_data · scopes read_content and read_online_store_navigation. Returns exactly what the published storefront reads from data/menus.json, data/pages.json and data/blogs.json, built from the live database — useful for previewing a theme that has not been published.
  • menus: sorted by handle. Links that no longer resolve are omitted with their nested links. resource is null for frontpage, catalog, collections and http; an article resource also carries blog_handle.
  • pages: published pages only.
  • blogs: every blog, with its published articles newest first; tags is the sorted union of those articles’ tags; image is null when a post has no featured image.

Webhooks