Weaverse Content API
Read and edit live Weaverse content (projects, pages, theme settings, languages) outside the Studio editor, over an authenticated REST API. Use it for bulk edits, AI/automation content pipelines, and pushing updates into a project that already exists.
- Base URL:
https://studio.weaverse.io/api/v1/content - Machine spec:
GET https://studio.weaverse.io/api/v1/content/openapi.json(OpenAPI 3.1, no auth) — the authoritative contract - Full endpoint details:
references/endpoints.md. Rich-text/Portable Text details:references/portable-text.md. - Helper script:
scripts/weaverse_content_api.mjs(zero-dependency, Node 18+).
The one thing you must understand first
The Content API edits live content — and it can also create pages.
Two write paths exist:
PATCH .../pages/:type/*handleshallow-mergesdatainto items on the page. Existing ids are updated; an unknown id is created when its entry also supplies atype(the component type).childrenmay be supplied to relink an item's children in the same request — each entry needs anidthat already belongs to the page or is created in the same request.POST /projects/:projectId/pagescreates a page — aCUSTOMpage (bespoke merchant page, blank root) or a resource-backed template page (PRODUCT/COLLECTION/PAGE/BLOG/ARTICLE, a per-resource override that clones the project's shared default template or abasedOnsource page).
So the lifecycle is:
Create initial structure → import a project JSON into Studio (generating-weaverse-project-json)
OR create pages one by one via POST /projects/:projectId/pages
Update content afterwards → Content API (PATCH page content, incl. new typed items)
There is no "create project" endpoint — a project must already exist (from Weaverse Builder) before the API can touch it.
When to Use
- Push AI-generated or translated copy into an existing live project
- Bulk-edit content across many pages/items
- Read current page content/items to diff or round-trip
- Add a new typed item to a page (via
PATCHwith atypeon the new id) or relinkchildren - Create a
CUSTOMpage or a resource-backed template page (POST /projects/:projectId/pages) - Delete pages in bulk
- Upload an image/video to Shopify and reference its CDN URL in a Weaverse item
Do not use it to create projects — a project must already exist in Weaverse Builder.
Authentication
Every endpoint except openapi.json needs a bearer token:
Authorization: Bearer <WEAVERSE_API_KEY>
- Get the key from Weaverse Studio → Dashboard → Account/Settings → API Keys.
- A token is scoped to one shop. Requests for a project owned by another shop return
403 FORBIDDEN. - The same token also authorizes the Shopify proxy (see "Upload resources to Shopify").
- Store it in an env var (
WEAVERSE_API_KEY). Never hardcode it, never pass it as a?apiKey=query param outside local testing — query params leak into server/CDN logs.
Core update workflow
Read before you edit. To change existing content you must target real item ids, so read the page first — you cannot patch blindly. New items are the one exception: they use a fresh id plus a type, and must fit the page tree (a children reference has to point at an id already on the page or created in the same request).
- Find the project —
GET /projects, match by name, keep itsid. - Pick a locale —
GET /projects/:projectId/languages. Keep theisDefault: truecode (e.g.en-us). You need it for the next steps. - Read the page —
GET /projects/:projectId/pages/:type/*handle?locale=<code>. Always passlocale. With nolocalethe resolver only tries the empty locale and the legacy defaulten-us, so a market-first project or any project whose default locale isn'ten-usreturnsPAGE_NOT_FOUNDeven though the page exists. The defaultweaverseformat already returns every item with itsid— that id is exactly what the patch needs, so?meta=trueis not required (it only matters forportable-textreads). - Build the patch — for each item you want to change, send only the fields that change inside
data(it shallow-merges, so untouched fields stay). To create a new item, give it a freshidand supply itstype(component type). To relink children, addchildrenwith the child ids (each must already belong to the page or be created in the same request). Include the samelocaleyou read with:{ "locale": "en-us", "items": [ { "id": "itm1", "data": { "heading": "New heading" } }, { "id": "itm-new", "type": "Hero", "data": { "heading": "Fresh section" }, "children": [{ "id": "itm1" }] } ] } - Patch —
PATCH /projects/:projectId/pages/:type/*handle(usePOSTif your client/proxy can't send aPATCHbody). The page is resolved with the same locale rules as the read — a missing/wronglocalecan hitPAGE_NOT_FOUNDor patch the wrong locale's page. Max 100 items per request — chunk larger edits. - Check the response —
{ object: "page_update", updated, notFound, updatedIds, notFoundIds }. A non-emptynotFoundIdsmeans those ids couldn't be resolved on the page (wrong page, wrong locale, stale ids, or a new id sent without atype) — re-read the page with the rightlocale, don't retry the same ids.
A successful patch invalidates caches and goes live through api.weaverse.io — the same path a Studio save takes.
Page addressing
Pages are addressed by Prisma page type + handle:
INDEX, PRODUCT, ALL_PRODUCTS, COLLECTION, COLLECTION_LIST, PAGE, BLOG,
ARTICLE, CART, CUSTOMER, NOT_FOUND, PASSWORD, SEARCH, CUSTOM
- Singletons (
INDEX,ALL_PRODUCTS,COLLECTION_LIST,CART,CUSTOMER,NOT_FOUND,PASSWORD,SEARCH) — one page per project, omit the handle. - CUSTOM — addressed by its path (the splat may contain slashes, e.g.
blogs/news). - Templated (
PRODUCT,COLLECTION,PAGE,BLOG,ARTICLE) — keep a shared default template at the empty handle, so a missing handle is rejected (it won't silently edit the template). Pass the real handle.
Locale always matters. On reads/updates, always pass a real locale code (from List languages). In list-pages responses, a row's locale may be null for market-first projects (rows are keyed by market, not locale) — don't echo null back; pass a real code and let resolution map it to the market (e.g. locale=en-us resolves market us).
See references/endpoints.md for the full endpoint list, query params, and response shapes.
Upload resources to Shopify
The Content API itself has no upload endpoint. To get media into a Weaverse item, upload it to Shopify first, then reference the returned CDN URL.
Upload goes through the Weaverse Shopify proxy, which accepts the same Weaverse token:
POST https://studio.weaverse.io/api/admin-graphql
Authorization: Bearer <WEAVERSE_API_KEY>
Content-Type: application/json
The body is a normal Shopify Admin GraphQL request ({ "query": "...", "variables": {...} }). Upload is the standard two-step Shopify flow:
stagedUploadsCreate→ get aurl+parameters(a presigned target) and aresourceUrl.- Upload the file bytes to that staged
urlwith the returnedparameters(multipart POST, not through the proxy). fileCreatewithoriginalSource: <resourceUrl>→ Shopify ingests it and returns the permanent CDN file.- Read back the file's
image.url/sourcesand put that CDN URL into the Weaverse itemdatavia the update workflow above.
Reference implementation in the builder repo: app/backend/admin/file.server.ts (generateStagedUploadLinks → stagedUploadsCreate, then fileCreate). When in doubt, mirror its mutations and field selections.
Alternatively, when a connected Shopify MCP is available, its image-upload /
graphql_mutationtools do the same job without the proxy. Use whichever is connected.
Helper script
scripts/weaverse_content_api.mjs wraps auth and the common calls. It reads WEAVERSE_API_KEY from the environment.
export WEAVERSE_API_KEY=...
node scripts/weaverse_content_api.mjs projects
node scripts/weaverse_content_api.mjs languages <projectId>
node scripts/weaverse_content_api.mjs theme <projectId>
node scripts/weaverse_content_api.mjs pages <projectId> [type]
node scripts/weaverse_content_api.mjs page <projectId> <type> [handle] [locale] # reads with ?locale
node scripts/weaverse_content_api.mjs update <projectId> <type> <handle> <patch.json>
node scripts/weaverse_content_api.mjs delete <projectId> <type> <handle...>
Use it to inspect a project quickly and to apply patch files. For anything the script doesn't cover, call the REST endpoints directly or read openapi.json.
Red Flags
- Trying to create a project via the API — there is no create-project endpoint. Projects are created in Weaverse Builder; the API edits them.
- Sending a new item id without a
typein a PATCH — unknown ids are created only whentypeis supplied; otherwise they land innotFoundIds. Forchildrenentries, each id must already be on the page or be created in the same request. - Patching an existing item without reading its id first — you must target a real item id. Read the page first (with the right
locale); the defaultweaverseread already includes every itemid, so you do not need?meta=true. (Creating a new item is different: fresh id +type.) - Omitting
localeon a page read/update — with nolocalethe resolver only tries the empty locale and legacyen-us, so non-en-usor market-first projects returnPAGE_NOT_FOUNDeven when the page exists. Always pass a reallocalefrom List languages. - "My published edit is missing from the API" — silent wrong-locale page (not a 404). Content is stored per locale (
PageAssignmentkeyed byprojectId, locale, type, handle). When a merchant edits/publishes with a market locale selected (e.g.en-us), the change saves to theen-usassignment, not the base locale"". Becauselocaledefaults to"", a request like…/pages/PRODUCT/default(no?locale) can succeed (200) but return the base-locale assignment — a different, often empty/stale page — so the edit looks "missing" even though it published fine. This is distinct fromPAGE_NOT_FOUND: the request works, it just returns the wrong locale's page. Fix: always pass?locale=<market>(e.g.en-us). To find which locale a product/page uses: Studio top-bar template dropdown (shows active template + "Assigned to N products") with the market/locale selector beside it; orGET /pages(locale per row); or readdata-weaverse-template-idfrom the live storefront HTML. Verified live on a market-first project: the edit lived onlocale=en-us(active, freshly published) while baselocale=""was the stale page the no-locale call returned. - Echoing back
locale: null— list-pages rows can benullfor market-first projects. Don't sendnull; pass a real code and let resolution map it to the market. - Adding
?meta=truefor normal edits — it only affectsportable-textreads (where it restores_weaverse.id). On aweaverse-format read it changes nothing. - Ignoring
notFoundIdsin the response — it means your ids aren't on the resolved page (wrong page, wrong locale, or stale ids). Re-read with the right locale, don't retry. - Sending more than 100 items in one update (or 500 targets in one delete) — chunk the request.
- Editing a templated type with an empty handle — rejected by design. Pass the real handle.
- Hardcoding the token or using
?apiKey=— useAuthorization: Bearerfrom an env var. - Replacing whole
dataobjects — updates shallow-merge. Send only changed fields; don't resend the entiredataand risk wiping nested values you didn't read. - Putting a non-Shopify URL into a media field after "upload" — finish the
fileCreatestep and use the returned Shopify CDN URL, not the staged/temporaryresourceUrl.
Related skills
generating-weaverse-project-json— creates the import JSON that establishes the structure this API then updates. The item ids you patch here come from that JSON (or from a page read back with the rightlocale).cloning-websites-to-weaverse/figma-to-weaverse— produce the section plan that feeds the JSON generator.