@wix/agent-skills 1.30.0 → 1.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/SKILL.md +7 -8
  3. package/skills/wix-app/references/DASHBOARD_PAGE.md +1 -1
  4. package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +11 -50
  5. package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +14 -10
  6. package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +5 -8
  7. package/skills/wix-app/references/dashboard-page/TABLE_STATE.md +5 -4
  8. package/skills/wix-headless-kit/SKILL.md +20 -12
  9. package/skills/wix-headless-kit/guides/api-run.md +178 -0
  10. package/skills/wix-headless-kit/guides/existing-site.md +3 -2
  11. package/skills/wix-headless-kit/guides/reference-mode.md +9 -1
  12. package/skills/wix-headless-kit/install/AGENTS.md +7 -5
  13. package/skills/wix-headless-kit/install/attach.mjs +3 -3
  14. package/skills/wix-headless-kit/install/context.mjs +13 -3
  15. package/skills/wix-headless-kit/install/deploy.mjs +3 -3
  16. package/skills/wix-headless-kit/install/pins.json +1 -1
  17. package/skills/wix-headless-kit/install/setup.mjs +2 -2
  18. package/skills/wix-headless-kit/install/templates.mjs +2 -1
  19. package/skills/wix-headless-templates/blog/seed/SEED.md +1 -1
  20. package/skills/wix-headless-templates/blog/seed/seed-blog.mjs +2 -2
  21. package/skills/wix-headless-templates/bookings/seed/SEED.md +1 -1
  22. package/skills/wix-headless-templates/bookings/seed/seed-bookings.mjs +2 -1
  23. package/skills/wix-headless-templates/donations/seed/SEED.md +1 -1
  24. package/skills/wix-headless-templates/donations/seed/seed-donations.mjs +3 -2
  25. package/skills/wix-headless-templates/events/seed/SEED.md +1 -1
  26. package/skills/wix-headless-templates/events/seed/seed-events.mjs +3 -2
  27. package/skills/wix-headless-templates/portfolio/seed/SEED.md +4 -2
  28. package/skills/wix-headless-templates/portfolio/seed/seed-portfolio.mjs +6 -6
  29. package/skills/wix-headless-templates/rentals/seed/SEED.md +1 -1
  30. package/skills/wix-headless-templates/rentals/seed/seed-rentals.mjs +2 -1
  31. package/skills/wix-headless-templates/restaurants/seed/SEED.md +1 -1
  32. package/skills/wix-headless-templates/restaurants/seed/seed-restaurants.mjs +4 -3
  33. package/skills/wix-headless-templates/shared/seed/images.mjs +63 -7
  34. package/skills/wix-headless-templates/storefront/seed/SEED.md +17 -5
  35. package/skills/wix-headless-templates/storefront/seed/seed-store.mjs +49 -16
  36. package/skills/wix-manage/SKILL.md +25 -15
  37. package/skills/wix-manage/references/automations/activation-status.md +52 -0
  38. package/skills/wix-manage/references/automations/api-catalog.md +81 -0
  39. package/skills/wix-manage/references/automations/build-and-manage-automations.md +34 -0
  40. package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +277 -363
  41. package/skills/wix-manage/references/custom-apps/use-app-skills-and-app-tools.md +3 -1
  42. package/skills/wix-manage/references/sites/send-feedback-to-wix.md +92 -0
  43. package/skills/wix-manage/references/sites/upload-static-site.md +258 -279
  44. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +7 -1
  45. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +7 -1
  46. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +7 -1
  47. package/skills/wix-headless-kit/guides/no-machine.md +0 -115
  48. package/skills/wix-manage/references/bookings/create-appointment-service.md +0 -243
  49. package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +0 -31
  50. package/skills/wix-manage/references/bookings/create-class-service.md +0 -222
  51. package/skills/wix-manage/references/bookings/create-course-service.md +0 -251
@@ -5,287 +5,300 @@ description: Publish a user's ready-made website — an index.html, a static bui
5
5
 
6
6
  # Upload a Website or HTML Files
7
7
 
8
- The user has a finished website as files — hand-written HTML, a static build, a
9
- zip, or the output of an AI site builder — and wants it live on Wix as a static,
10
- Wix-hosted site.
11
-
12
- ## Choose the route
13
-
14
- Five ways to get the files live; what you have decides which are open to you.
15
-
16
- | Option | Needs | Carries | The user ends up with |
17
- | --- | --- | --- | --- |
18
- | **A.** `curl` + CLI token → [into the account](#publish-into-the-users-account) | A shell; a Wix CLI login | Anything on disk | A site in their account, final URL |
19
- | **B.** `ExecuteWixAPI` → [into the account](#publish-into-the-users-account) | The Wix MCP | Files already in the conversation — text as is, small binaries as base64 — and anything downloaded from the site | A site in their account, final URL |
20
- | **C.** `curl` → [anonymous](#publish-anonymously) | A shell | Anything on disk | A live site for one hour; kept by a [claim](#claim-it-into-the-users-account) (through the Wix MCP or a CLI token) or the save link |
21
- | **D.** [The drop page](#the-drop-page) | Nothing | Whatever the user uploads | The same, after they upload it themselves |
22
- | **E.** [The headless skill](#keep-building-add-a-backend-when-you-need-one) | A shell; Node; a Wix CLI login | A project folder, source included (built for you) | A site in their account as a Wix Headless project, released with the Wix CLI, ready for Wix Business Solutions |
23
-
24
- What sets them apart:
25
-
26
- - **`curl -F` streams files from disk** — the bytes never pass through you: any type, any
27
- number, up to the [limits](#what-the-upload-accepts-and-how-it-fails).
28
- - **`ExecuteWixAPI` has no filesystem.** The Wix MCP's tool runs JavaScript whose
29
- `wix.request` calls carry the user's login — no install, no token — but every
30
- byte is written out inside the call. That costs nothing extra for a page you
31
- generated or the user pasted (it's already in the conversation); for files on
32
- disk it means reading them in and writing them back out, and each change
33
- resends all of it. Files travel in the tool's `files` param — text raw, a
34
- binary file (PNG, JPG, fonts) as base64 — and `wix.multipart()` builds the
35
- upload body (below). Base64 costs about a third more than the file and every
36
- byte is tokens, so it suits small assets (an icon, a logo, a font) and files
37
- you downloaded from the site to change; a photo goes in by absolute URL
38
- (`<img src="https://…">`) or with `curl` from a shell. The bundle is capped at
39
- 4M characters.
40
- - **A CLI login** is one approval by the user in the browser: run
41
- `npx @wix/cli login` and have them approve; `npx @wix/cli token` then prints a
42
- token (see [Before the calls](#before-the-calls)). It also unlocks later
43
- changes from disk and
44
- [Keep building](#keep-building-add-a-backend-when-you-need-one).
45
- - **Anonymous** needs no identity, but the record expires after an hour and the
46
- URL changes on claim.
47
- - **The headless skill** is the heaviest — an install, a project, a build — and
48
- the only one that takes framework source as is and leaves a project ready for a
49
- backend. A drop site can still move to it [later](#keep-building-add-a-backend-when-you-need-one).
50
-
51
- So: a site already in the conversation — pages, styles, a logo — with the Wix
52
- MCP connected — B, shell or not. Static files on disk — A with a CLI login, else C (claimed through
53
- the Wix MCP when it's connected). A framework project (a `package.json`), or a
54
- site that needs stores, bookings, a CMS or members from the start — E. Files out
55
- of your reach, or nothing above fits — D.
56
-
57
- Publishing yourself beats the drop page whenever an option fits — the user gets a
58
- live site without uploading anything. Never report an upload you couldn't
59
- perform; whenever a route fails partway, hand over the drop page.
8
+ The user has a finished website — hand-written HTML, a static build, a zip, a
9
+ project an AI builder exported — and wants it live on Wix. Every route below ends
10
+ the same way: a headless site in the user's Wix account that serves those files
11
+ at a `*.wix-site-host.com` URL.
12
+
13
+ ## What decides the route
14
+
15
+ Three things about where you run decide it:
16
+
17
+ - **Does your shell reach Wix?** `curl -sI https://www.wixapis.com` answers with an
18
+ HTTP status line. When it fails, or there is no shell, it doesn't.
19
+ - **Is the Wix MCP connected?** Its tools are in your tool list: `ExecuteWixAPI`,
20
+ and usually `UploadHeadlessWebsiteFiles`.
21
+ - **Do your files travel as files?** Some clients resolve a tool's `attachments`
22
+ param themselves: a file the user attached, or one you wrote to disk, reaches
23
+ the upload tool as the file. Elsewhere `attachments` comes back unresolved, and
24
+ every file you send through the MCP is text you write into the call.
25
+
26
+ | Your situation | Route |
27
+ | --- | --- |
28
+ | The shell reaches Wix | [Create the site](#create-the-site) and [drop the files with `curl`](#drop-with-curl) and a CLI token; without a login, [publish anonymously](#publish-anonymously). A site of text files can take the MCP route below instead, with no login. |
29
+ | The shell doesn't reach Wix, or there is no shell, and the Wix MCP is connected | [Create the site](#create-the-site) and [drop through the MCP](#drop-through-the-wix-mcp): the files themselves when they travel as files, a text bundle when they don't. |
30
+ | A project that needs a build (a `package.json`) | Build it where its dependencies install, then drop the build output. When the shell reaches Wix, the [headless skill](#connect-a-backend) builds and releases it. When it can't be built anywhere you are, ask the user for the build output. |
31
+ | None of these, or every route failed | The [drop page](#the-drop-page): the user uploads the files. |
32
+
33
+ When files don't travel as files, writing them costs nothing for a page already
34
+ in the conversation and a read and a rewrite for text files on disk; images never
35
+ travel as text (see [Images](#images)). Publishing yourself beats the drop page
36
+ whenever a route fits. Never report an upload you couldn't perform; when a route
37
+ fails partway, hand over the drop page.
60
38
 
61
39
  ## Before the calls
62
40
 
63
- The `curl` examples here and in the anonymous route read shell variables.
64
-
65
- - **`$ACCESS_TOKEN`** is the user's access token, from wherever you have it. A
66
- token from the Wix CLI (`npx @wix/cli token`) lasts 15 minutes; running the
67
- command again returns a valid one, refreshed if needed, so set `$ACCESS_TOKEN`
68
- again when it may have expired, and always after a `403`.
69
- - **`$META_SITE_ID`, `$UPLOAD_ID`, …** come from the previous response; set each
70
- one before the next call.
71
-
72
- In an `ExecuteWixAPI` script there is no token to handle, and the ids go into the
73
- URL as values; a `$NAME` there is sent as is.
74
-
75
- ## Publish into the user's account
76
-
77
- ### 1. Create the site
41
+ - **With a shell that reaches Wix**, `$ACCESS_TOKEN` is the user's token: run
42
+ `npx @wix/cli@latest login` once (the user approves in the browser), then
43
+ `npx @wix/cli@latest token` prints a token that lasts 15 minutes. Run it again
44
+ when the token may have expired, and always after a `403`.
45
+ - **Through the Wix MCP**, `ExecuteWixAPI` runs JavaScript
46
+ whose `wix.request` calls carry the user's login: no token. Ids go into URLs as
47
+ values; a `$NAME` there is sent as is. `wix.request` returns `{ status, data }`.
48
+ - **`$AGENT`** is a short lowercase slug naming you (`claude-code`, `cursor`,
49
+ `codex-cli`, `chatgpt`, `claude-ai`; `unknown-agent` if you can't tell). Every drop and upload
50
+ carries `?campaign=mcp&agent=$AGENT`, which attributes the site to you in Wix's
51
+ reports.
52
+ - **`$META_SITE_ID`, `$UPLOAD_ID`, …** come from the previous response.
53
+
54
+ **Reading the pages this recipe links.** A Wix skill you have installed is read
55
+ from disk. Otherwise a `www.wix.com/skills/…` or `dev.wix.com/docs/…` page is read
56
+ with the Wix MCP's `ReadFullDocsArticle` when the MCP is connected; it returns the
57
+ page whole. A web fetch is the fallback.
58
+
59
+ ## Create the site
78
60
 
79
61
  The [Create Headless Site](create-headless-site.md) call with no Wix Business
80
- Solutions. Name it after the page's `<title>`, and keep `"origin": "drop"`: it marks
81
- the site as a dropped one, the same as the drop page and the anonymous route do.
62
+ Solutions. Name it after the page's `<title>` and keep `"origin": "drop"`.
82
63
 
83
64
  ```bash
84
65
  curl -sS -X POST "https://www.wixapis.com/headless-business-setup/v1/headless-business/provision" \
85
66
  -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Content-Type: application/json' \
86
67
  -d '{"origin":"drop","newMetasite":{"namingStrategy":{"metaSiteName":"Northwind Studio"},"seedOptions":[]},
87
68
  "synchronousSteps":["SET_METASITE_NAME","CONFIGURE_HEADLESS_APP"]}'
69
+ # {"metaSiteId":"7597a72e-bedc-4d86-af86-9a25d11f0632","appId":"9f0a8b59-1acb-4a67-aafd-f8602259cbfa"}
88
70
  ```
89
71
 
90
- ```json
91
- { "metaSiteId": "7597a72e-bedc-4d86-af86-9a25d11f0632", "appId": "9f0a8b59-1acb-4a67-aafd-f8602259cbfa" }
72
+ In a script, the same call:
73
+
74
+ ```javascript
75
+ const created = await wix.request({ scope: 'account', method: 'POST',
76
+ url: 'https://www.wixapis.com/headless-business-setup/v1/headless-business/provision',
77
+ body: { origin: 'drop', newMetasite: { namingStrategy: { metaSiteName: 'Northwind Studio' }, seedOptions: [] },
78
+ synchronousSteps: ['SET_METASITE_NAME', 'CONFIGURE_HEADLESS_APP'] } });
79
+ // created.data.metaSiteId
92
80
  ```
93
81
 
94
- ### 2. Drop the files — the site goes live
82
+ The site is in the user's account. When it goes live, give the user its
83
+ `siteUrl` and the dashboard, `https://manage.wix.com/dashboard/{metaSiteId}`.
84
+
85
+ ## Drop with curl
95
86
 
96
87
  One multipart request uploads and releases. Each file is a part named `files`
97
- whose **filename is its path relative to the site root** — that is how
98
- subdirectories survive; with `curl`, set it with `;filename=` whenever it isn't
99
- just the basename. A single `.zip` part works too — it's unpacked server-side, a
100
- single wrapping folder stripped.
88
+ whose **filename is its path relative to the site root**; that is how
89
+ subdirectories survive. A single `.zip` part works too: it is unpacked
90
+ server-side, a single wrapping folder stripped.
101
91
 
102
92
  ```bash
103
93
  curl -sS -X POST \
104
- "https://www.wixapis.com/headless-business-setup/v1/headless-business/$META_SITE_ID/drop" \
94
+ "https://www.wixapis.com/headless-business-setup/v1/headless-business/$META_SITE_ID/drop?campaign=mcp&agent=$AGENT" \
105
95
  -H "Authorization: Bearer $ACCESS_TOKEN" \
106
96
  -F "files=@index.html;filename=index.html" \
107
97
  -F "files=@assets/styles.css;filename=assets/styles.css" \
108
98
  -F "files=@assets/logo.png;filename=assets/logo.png"
99
+ # {"uploadId":"c0d5b3bb-…","siteUrl":"https://headless-zjfqzddjtww-northwind-1406.wix-site-host.com"}
109
100
  ```
110
101
 
111
- ```json
112
- { "uploadId": "c0d5b3bb-60a9-43f9-9d27-7ca8df967825",
113
- "siteUrl": "https://headless-zjfqzddjtww-northwind-1406.wix-site-host.com" }
114
- ```
102
+ `siteUrl` is final.
103
+
104
+ ## Drop through the Wix MCP
105
+
106
+ ### With the Wix MCP's upload tool
107
+
108
+ When the Wix MCP lists `UploadHeadlessWebsiteFiles`, it is the drop: pass it the
109
+ `metaSiteId` from [Create the site](#create-the-site) as `siteId`, and the files.
110
+ It replaces the whole file set, stamps the attribution itself, and returns
111
+ `siteUrl` and the dashboard link. It creates no site and makes no other call.
115
112
 
116
- Option B — the same two calls as one `ExecuteWixAPI` script, for files already
117
- in the conversation. The files go in the tool's **`files` param**, not in
118
- `code`: one bundle, each file introduced by a line `=== FILE: <path> ===` and
119
- followed by its raw text, `<path>` relative to the site root. A binary file is
120
- introduced by `=== FILE: <path> base64 ===` and followed by its base64. Nothing
121
- in it is escaped, so quotes, backticks, `${}` and backslashes arrive as written.
113
+ - **When your files travel as files**, pass them as `attachments`: zip the whole
114
+ site folder, images included, and pass the zip as one attachment. It is
115
+ unpacked, a wrapping folder stripped, up to 10 MB per call. Nothing is written
116
+ out by you. Where `attachments` doesn't resolve, a path passed in it fails:
117
+ your files don't travel as files, and they go in the bundle below.
118
+ - **When they don't**, pass a `files` text bundle: each file starts with a line
119
+ `=== FILE: <path> ===` followed by its raw text, `<path>` relative to the site
120
+ root. Nothing in it is escaped. It carries text files: HTML, CSS, JavaScript,
121
+ SVG. Other images reach the site as [Images](#images) says.
122
122
 
123
123
  ```
124
124
  === FILE: index.html ===
125
125
  <!doctype html><html><head><title>Northwind Studio</title>
126
- <link rel="stylesheet" href="assets/styles.css"></head><body><h1>Northwind Studio</h1><img src="assets/logo.png"></body></html>
126
+ <link rel="stylesheet" href="assets/styles.css"></head><body><h1>Northwind Studio</h1><img src="assets/logo.svg"></body></html>
127
127
  === FILE: assets/styles.css ===
128
128
  body { font-family: sans-serif; margin: 0; padding: 4rem; }
129
- === FILE: assets/logo.png base64 ===
130
- iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
129
+ === FILE: assets/logo.svg ===
130
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><circle cx="5" cy="5" r="4"/></svg>
131
131
  ```
132
132
 
133
- In `code`, the bundle is the `files` global (`[{ path, content, encoding? }]`,
134
- `encoding: 'base64'` on the binary entries) and `wix.multipart()` turns it into
135
- the upload body: one part named `files` per file, the path as its filename, a
136
- content type guessed from the extension, base64 entries decoded to their bytes.
133
+ The bundle can't carry a file with a line that reads exactly `=== FILE: … ===`.
134
+
135
+ ### From an ExecuteWixAPI script
136
+
137
+ Without the upload tool, both calls run in one script. The same bundle goes in
138
+ the tool's **`files` param**, never in `code`; in `code` it is the `files` global
139
+ (`[{ path, content, encoding? }]`), and `wix.multipart()` turns it into the body.
137
140
 
138
141
  ```javascript
139
142
  async function run() {
143
+ const agent = 'your-agent-slug'; // $AGENT
140
144
  const created = await wix.request({ scope: 'account', method: 'POST',
141
145
  url: 'https://www.wixapis.com/headless-business-setup/v1/headless-business/provision',
142
146
  body: { origin: 'drop', newMetasite: { namingStrategy: { metaSiteName: 'Northwind Studio' }, seedOptions: [] },
143
147
  synchronousSteps: ['SET_METASITE_NAME', 'CONFIGURE_HEADLESS_APP'] } });
144
- const mp = wix.multipart(); // the `files` param; or pass [{ path, content | base64 | bytes }]
148
+ const mp = wix.multipart(); // from the `files` param; or pass [{ path, content | base64 | bytes }]
145
149
  return await wix.request({ scope: 'account', method: 'POST',
146
- url: `https://www.wixapis.com/headless-business-setup/v1/headless-business/${created.data.metaSiteId}/drop`,
147
- headers: { 'Content-Type': mp.contentType },
148
- body: mp.body });
150
+ url: `https://www.wixapis.com/headless-business-setup/v1/headless-business/${created.data.metaSiteId}/drop?campaign=mcp&agent=${agent}`,
151
+ headers: { 'Content-Type': mp.contentType }, body: mp.body });
149
152
  }
150
153
  ```
151
154
 
152
- `wix.request` returns `{ status, data }`; read a response's fields from `data`
153
- (`created.data.metaSiteId`). Don't build the multipart body by hand — a missing
154
- `\r\n` or a boundary mismatch is a bare `500` — and don't put file text inside
155
- `code` as string literals: that second layer of escaping is what corrupts
156
- backslashes and `${}`. Don't put image bytes in `code` either — they're a
157
- `base64` entry in `files`. The one thing `files` cannot carry is a file with a
158
- line that reads exactly `=== FILE: … ===`.
159
-
160
- `siteUrl` is the site's final address — it's already in the user's account. Give
161
- the user two links: `siteUrl`, and its dashboard at
162
- `https://manage.wix.com/dashboard/{metaSiteId}`.
163
-
164
- ### Change it later
165
-
166
- Re-run step 2 on the same `metaSiteId` with the **full** file set: each drop
167
- replaces the site's files (a file left out is gone), and `siteUrl` stays the same.
168
- Never create another site for a change. The same call updates any site the user
169
- owns that was published this way, including one claimed from an
170
- [anonymous publish](#publish-anonymously). For a site from an earlier conversation,
171
- find its `metaSiteId` with the [Query Sites](#claim-it-into-the-users-account) call
172
- below, matching the site's name or `viewUrl`.
173
-
174
- **When you no longer have the files** (a small change to a site from an earlier
175
- conversation), download what the site serves, edit it, and drop the full set
176
- back. Leave out `wix.config.json`; the download adds it, and it isn't part of the
177
- site.
155
+ A zip passed through `attachments` is one entry; unpack it first:
156
+ `wix.multipart(await wix.unzip(files[0].content))`. Don't build a multipart body
157
+ by hand while `wix.multipart()` exists (a malformed one is a bare `500`).
158
+
159
+ **An older `ExecuteWixAPI` with no `files` param** still has `wix.multipart()`:
160
+ pass it the entries written in `code`, `[{ path, content }]`, the text as template
161
+ literals. Escape `\` as `\\` first, then `` ` `` as `` \` `` and `${` as `\${`; an
162
+ unescaped `\` is dropped or reinterpreted. Only when
163
+ `typeof wix.multipart !== 'function'` is the body a string you build: parts named
164
+ `files` with the path as `filename`, lines ending in `\r\n`, the body ending with
165
+ `--<boundary>--`, and `Content-Type: multipart/form-data; boundary=<the same
166
+ boundary>`.
167
+
168
+ ## Change it later
169
+
170
+ On the same `metaSiteId`, never a new site. Each drop replaces every file, and
171
+ `siteUrl` stays.
172
+
173
+ - **With a shell that reaches Wix**, edit the files and drop the full set with
174
+ `curl`.
175
+ - **Through the MCP**, change the site in one `ExecuteWixAPI` script: download
176
+ what it serves, edit in code, and drop the full set back. Only the edit is
177
+ written; images travel back as they are. When your files travel as files, a
178
+ new zip of the whole site through the upload tool works as well. A full
179
+ rewrite of the text bundle is for a site rewritten wholesale.
180
+
181
+ For a site from an earlier conversation, find its `metaSiteId` with
182
+ [Query Sites](#claim-it-into-the-users-account), matching the name or `viewUrl`.
183
+ The download holds what the site serves plus a `wix.config.json`, which is left
184
+ out of the drop:
178
185
 
179
186
  ```bash
180
187
  curl -sSL -o current.zip \
181
188
  "https://www.wix.com/_api/wixstro-deployments/v1/instant-sites/$META_SITE_ID/download.zip"
182
- unzip -o current.zip -d current # the site's files + wix.config.json
183
189
  ```
184
190
 
185
- In an `ExecuteWixAPI` script, request the same URL with `responseType: 'base64'`
186
- and unzip it in memory with `wix.unzip()`. Each entry is `{ path, bytes, text() }`:
187
- `bytes` is the file as stored, `text()` decodes it when it's text. Don't decode
188
- every entry — an image run through a text decoder is corrupted.
191
+ In a script, request that URL with `responseType: 'base64'` and pass `r.data` to
192
+ `wix.unzip()`. Each entry is `{ path, bytes, text() }`; call `text()` only on text
193
+ files, since a decoded image is corrupted. Entries go back to `wix.multipart()` as
194
+ they are, an edited one as `{ path, content }`:
189
195
 
190
196
  ```javascript
191
197
  const r = await wix.request({ scope: 'account', method: 'GET', responseType: 'base64',
192
198
  url: `https://www.wix.com/_api/wixstro-deployments/v1/instant-sites/${metaSiteId}/download.zip` });
193
- const entries = await wix.unzip(r.data);
194
- // entries: [{ path: 'index.html', bytes, text() }, { path: 'assets/logo.png', bytes, text() }, { path: 'wix.config.json', … }]
195
- ```
196
-
197
- Edit in memory — text through `text()`, binaries left as they are — then drop
198
- the full set back in the same script, leaving `wix.config.json` out. Entries go
199
- to `wix.multipart()` as they are; an edited file replaces its entry with
200
- `{ path, content }`:
201
-
202
- ```javascript
203
- const edited = entries.filter((e) => e.path !== 'wix.config.json').map((e) =>
204
- e.path === 'index.html' ? { path: e.path, content: e.text().replace('Northwind Studio', 'Northwind Studio — Est. 1998') } : e);
205
- const mp = wix.multipart(edited);
206
- await wix.request({ scope: 'account', method: 'POST', headers: { 'Content-Type': mp.contentType }, body: mp.body,
207
- url: `https://www.wixapis.com/headless-business-setup/v1/headless-business/${metaSiteId}/drop` });
199
+ const entries = (await wix.unzip(r.data)).filter((e) => e.path !== 'wix.config.json').map((e) =>
200
+ e.path === 'index.html' ? { path: e.path, content: e.text().replace('Welcome', 'Bloom Cafe — Now Open') } : e);
201
+ const mp = wix.multipart(entries);
202
+ // then the drop call: headers { 'Content-Type': mp.contentType }, body mp.body
208
203
  ```
209
204
 
210
- The live URL itself (`*.wix-site-host.com`) can't be read from a script; this
211
- download is the way back to the files. Don't rebuild the site from memory.
205
+ Never rebuild a site from memory.
206
+
207
+ ## Connect a backend
208
+
209
+ A dropped site is static. Stores, payments, bookings, a CMS, members or forms
210
+ need a **Wix Headless project**, which the headless skill builds and releases on
211
+ the same site, appId and URL.
212
+
213
+ **A mock that shows a business solution: publish it, then ask before connecting.**
214
+ Any control on the page that promises what static files can't deliver —
215
+ something to buy, book, order, join, submit or pay — is a Wix Business Solution
216
+ the page pretends to have. First get the site live from what the user gave you.
217
+ Then, with the live URL, name each such control, the solution that would make it
218
+ work, and what connecting it involves: what gets created from the page's own
219
+ content (the products, services or form). Ask whether to go ahead, and connect only after the user says
220
+ yes; a request that already asks for it (a working shop, real bookings) is that
221
+ yes. A page that promises nothing beyond its content stops at the pages.
222
+
223
+ A site published anonymously is [claimed](#claim-it-into-the-users-account)
224
+ before a backend: the headless skill works on a site in the user's account.
225
+
226
+ The headless skill starts at its cold-start page,
227
+ `https://www.wix.com/skills/headless-cold-start/headless-kit.md` (read it as
228
+ [Before the calls](#before-the-calls) says). It installs the skills, runs a
229
+ bootstrap that checks the Wix CLI login, and hands off to the kit's `SKILL.md`,
230
+ which reads the folder, deploys the solution's code, seeds, builds and releases.
231
+ Its commands run:
232
+
233
+ - **With a shell that reaches Wix**, in a folder holding the site, from the
234
+ site's own download:
235
+
236
+ ```bash
237
+ curl -sSL -o project.zip \
238
+ "https://www.wix.com/_api/wixstro-deployments/v1/instant-sites/$META_SITE_ID/download.zip"
239
+ unzip project.zip -d project
240
+ ```
241
+ - **Through the Wix MCP alone**, the kit's API-call guide,
242
+ `https://www.wix.com/skills/wix-headless-kit/guides/api-run.md`, does the same
243
+ run as Wix API calls through the Wix MCP, with the frontend dropped as this
244
+ recipe says.
245
+
246
+ ## Images
247
+
248
+ You never write an image's bytes yourself, as base64 or in any other form: model
249
+ output corrupts them past a few thousand characters, and every byte costs tokens.
250
+ An SVG is text and goes in like a page. Every other image reaches Wix one of four
251
+ ways:
252
+
253
+ 1. **With a shell that reaches Wix**, in the `curl` drop, from disk.
254
+ 2. **When your files travel as files**, inside the zip you pass the upload tool.
255
+ 3. **A public URL**: the page keeps the absolute URL.
256
+ 4. **The user uploads it** to the site's Media Manager. This is the way for images
257
+ that exist only as files you can't send. Publish the pages first, then name
258
+ the missing files, give the user
259
+ `https://manage.wix.com/dashboard/{metaSiteId}/media-manager`, and ask them to
260
+ tell you once they're uploaded. Then [list the site's files](../media/upload-media-to-wix.md),
261
+ match each to the page's reference by `displayName`, point the reference at the
262
+ file's `static.wixstatic.com` URL, and drop again.
263
+
264
+ Never stand in drawings or placeholders of your own for the user's images, and
265
+ never shrink or re-encode them. The closing message names every image still
266
+ missing.
212
267
 
213
268
  ## Publish anonymously
214
269
 
215
- No identity needed: the site is created under a temporary owner and lives for one
216
- hour unless the user keeps it.
217
-
218
- Generate `anonymousId` yourself — any UUID, **once per site, not once per
219
- request** — and reuse it, with the returned `metaSiteId`, for every call for that
220
- site. **Finish within one hour of step 1**: after that the record expires and
221
- every later call, claim included, returns `404`.
222
-
223
- ### 1. Create the site
270
+ With a shell that reaches Wix and no login: the site belongs to a temporary owner and
271
+ lives one hour unless the user keeps it. Generate `anonymousId` yourself, any
272
+ UUID, once per site, and reuse it with the returned `metaSiteId` for every call.
273
+ After one hour every call, claim included, returns `404`.
224
274
 
225
275
  ```bash
226
- curl -sS -X POST \
227
- "https://www.wixapis.com/headless-business-setup/v1/headless-business/anonymous/$ANONYMOUS_ID"
228
- ```
229
-
230
- ```json
231
- { "metaSiteId": "f0ad8672-09e9-4111-b6fe-070c70bd2df5",
232
- "projectId": "54528d34-b23c-4fbe-b07d-2c522350abd7" }
233
- ```
234
-
235
- Keep both: `metaSiteId` addresses the site, `projectId` builds the save link.
236
-
237
- ### 2. Upload the files
276
+ # 1. create
277
+ curl -sS -X POST "https://www.wixapis.com/headless-business-setup/v1/headless-business/anonymous/$ANONYMOUS_ID"
278
+ # {"metaSiteId":"f0ad8672-…","projectId":"54528d34-…"}
238
279
 
239
- Same multipart shape as the [drop](#2-drop-the-files--the-site-goes-live), to the
240
- upload path, with the attribution parameters (in a script: `wix.multipart()` with
241
- the `files` param, posted to this URL):
242
-
243
- ```bash
280
+ # 2. upload: the drop's multipart shape; nothing is live yet
244
281
  curl -sS -X POST \
245
282
  "https://www.wixapis.com/headless-business-setup/v1/headless-business/anonymous/$ANONYMOUS_ID/$META_SITE_ID/upload?campaign=mcp&agent=$AGENT" \
246
- -F "files=@index.html;filename=index.html" \
247
- -F "files=@assets/styles.css;filename=assets/styles.css"
248
- ```
249
-
250
- ```json
251
- { "uploadId": "03244542-d820-42f6-acfa-166c6658b1a6" }
252
- ```
253
-
254
- Keep `campaign=mcp` (the referral tag — don't change it) and set `agent` to your
255
- own identifier: a short, stable, lowercase-hyphenated slug for the coding agent or
256
- tool you are (e.g. `claude-code`, `cursor`, `codex-cli`, `windsurf`,
257
- `github-copilot`, or your product's name; `unknown-agent` if you can't name
258
- yourself). Nothing is live yet; this only stages and validates.
259
-
260
- ### 3. Release — the site goes live
283
+ -F "files=@index.html;filename=index.html" -F "files=@assets/styles.css;filename=assets/styles.css"
284
+ # {"uploadId":"03244542-…"}
261
285
 
262
- ```bash
286
+ # 3. release: the site goes live
263
287
  curl -sS -X POST \
264
288
  "https://www.wixapis.com/headless-business-setup/v1/headless-business/anonymous/$ANONYMOUS_ID/$META_SITE_ID/release" \
265
289
  -H 'Content-Type: application/json' -d "{\"uploadId\":\"$UPLOAD_ID\"}"
290
+ # {"siteUrl":"https://instant-hguwrvtcrniw-headlessstack-140d.wix-site-host.com"}
266
291
  ```
267
292
 
268
- ```json
269
- { "siteUrl": "https://instant-hguwrvtcrniw-headlessstack-140d.wix-site-host.com" }
270
- ```
271
-
272
- **To change it**, re-run steps 2–3 with the same `anonymousId` and `metaSiteId` and
273
- the full file set; `siteUrl` stays the same. Never go back to step 1 for a change.
274
- Iterate first, claim last: after a claim, changes are a [drop](#change-it-later),
275
- which needs the user's identity — a CLI token for files on disk.
276
-
277
- **When it's final:** with the user's identity, [claim it](#claim-it-into-the-users-account).
278
- Without it, stop here — a finished result. Give the user `siteUrl` plus the save
279
- link, which is how the site survives:
293
+ To change the site, repeat steps 2 and 3 with the full file set. When it's final,
294
+ claim it with the user's identity. Without one, give the user `siteUrl` and the
295
+ save link, which signs them in to keep the site. Whoever opens the link while
296
+ signed in to Wix gets the site, so give it only to the user:
280
297
 
281
298
  ```
282
299
  https://www.wix.com/live-headless-site/{projectId}?anonymousId={anonymousId}
283
300
  ```
284
301
 
285
- That page shows the site with a countdown and signs the user in to keep it. Treat
286
- the link as a secret — whoever opens it while signed in to Wix claims the site
287
- into *their* account — so give it only to the user who asked.
288
-
289
302
  ### Claim it into the user's account
290
303
 
291
304
  ```bash
@@ -294,14 +307,11 @@ curl -sS -X POST \
294
307
  -H "Authorization: Bearer $ACCESS_TOKEN"
295
308
  ```
296
309
 
297
- Returns `{}`. Claim after the release, never before — it consumes the anonymous
298
- record, so the anonymous endpoints stop working for this site; later changes go
299
- through the [drop](#change-it-later).
300
-
301
- **The URL changes on claim**: the step-3 host stops resolving. Read the new one
302
- with [Query Sites](https://dev.wix.com/docs/api-reference/account-level/sites/sites/query-sites),
303
- filtered to the `HEADLESS` namespace (the default query omits headless sites, and
304
- an `id` filter is rejected, so match the id yourself):
310
+ Claim last: it ends the anonymous record, and later changes are
311
+ [drops](#change-it-later). **The URL changes on claim.** Read the new `viewUrl`
312
+ with Query Sites in the `HEADLESS` namespace; the default query omits headless
313
+ sites and rejects an `id` filter, so match the id yourself, paging with
314
+ `metadata.cursors.next`:
305
315
 
306
316
  ```bash
307
317
  curl -sS -X POST "https://www.wixapis.com/site-list/v2/sites/query" \
@@ -309,85 +319,54 @@ curl -sS -X POST "https://www.wixapis.com/site-list/v2/sites/query" \
309
319
  -d '{"query":{"filter":{"namespace":"HEADLESS"},"cursorPaging":{"limit":100}}}'
310
320
  ```
311
321
 
312
- Take `viewUrl` from the entry whose `id` is your `metaSiteId` (page with
313
- `metadata.cursors.next` if needed), and give the user it plus
314
- `https://manage.wix.com/dashboard/{metaSiteId}`.
315
-
316
322
  ## The drop page
317
323
 
318
324
  ```
319
325
  https://www.wix.com/headless/drop?utm_campaign=mcp&agent=<your-agent-id>
320
326
  ```
321
327
 
322
- Keep `utm_campaign=mcp` and set `agent` to your own identifier, as in the
323
- [upload](#2-upload-the-files). The user drags in their files (no login), Wix hosts
324
- them on a live URL at once, and a banner offers to sign in and keep the site. Tell
325
- them the [requirements](#what-the-upload-accepts-and-how-it-fails) so it doesn't
326
- fail on the first try.
328
+ The user drags in the files without logging in, the site is live at once, and a
329
+ banner offers to keep it. Tell them the limits below first.
327
330
 
328
- ## What the upload accepts, and how it fails
331
+ When no route reached Wix from where you are, say what would let you publish it
332
+ yourself: a shell whose network reaches Wix, or the Wix MCP connected (the Wix
333
+ plugin or connector in a chat client).
329
334
 
330
- These apply to every route:
335
+ ## Limits and failures
331
336
 
332
- - **A top-level HTML file is required.** A lone top-level HTML of any name becomes
333
- the homepage; with more than one, an `index.html` must be among them.
337
+ - **A top-level HTML file.** A lone one of any name is the homepage; with more
338
+ than one, `index.html` must be among them.
334
339
  - **3 MB per file, 20 MB per site.**
335
- - **Static files only** — HTML, CSS, JS, images, fonts. Framework source that
336
- needs a build step (a `package.json`, React/Vue sources) must be built first;
337
- upload the build output, or take the project to the headless skill (option E).
338
-
339
- Failures come back as HTTP 400 with a code in `details.applicationError.code`:
340
- `MISSING_INDEX_HTML`, `FILE_TOO_LARGE`, `TOTAL_TOO_LARGE`. Beyond those:
341
-
342
- - **`403 PERMISSION_DENIED` on a drop** — the token expired or is missing (see
343
- [Before the calls](#before-the-calls)), or the site isn't the caller's. A site
344
- that's still anonymous is changed by upload + release, not by a drop.
345
- - **`500` on a drop or upload** — the multipart body was built by hand and is malformed (see the shape
346
- above). Rebuild it from the example; sending it again unchanged fails the
347
- same way.
348
- - **`404` on the anonymous route after step 1** — the hour passed or the site was
349
- claimed; start again.
350
-
351
- **If publishing fails for any reason you can't quickly fix, hand the user the
352
- [drop page](#the-drop-page).** Never leave them with a failed publish and no way
353
- forward.
354
-
355
- ## Keep building: add a backend when you need one
356
-
357
- The headless skill, `https://wix.com/headless/skill.md`, builds and releases a
358
- **Wix Headless project** with the Wix CLI: it adopts a project folder (a
359
- `package.json`, or an `index.html` at its root) into a new site, or takes a
360
- dropped one. A dropped site is **static**; when it needs a real backend — stores,
361
- payments, bookings, a CMS, members, forms — it moves to a headless project,
362
- keeping the same site, appId and URL. This is a choice the user makes when the
363
- need appears; static changes never need it, they're a [drop](#change-it-later).
364
-
365
- To move a dropped site, in a shell, once it's in the user's account:
340
+ - **Static files only** in a drop. A project that needs a build is built first,
341
+ and its build output is dropped.
366
342
 
367
- ```bash
368
- curl -sSL -o project.zip \
369
- "https://www.wix.com/_api/wixstro-deployments/v1/instant-sites/$META_SITE_ID/download.zip"
370
- unzip project.zip -d project # the site's files + wix.config.json
371
- ```
343
+ | Failure | Meaning |
344
+ | --- | --- |
345
+ | `400` `MISSING_INDEX_HTML`, `FILE_TOO_LARGE`, `TOTAL_TOO_LARGE` | In `details.applicationError.code`; fix the files. |
346
+ | `403 PERMISSION_DENIED` on a drop | The token expired or is missing, or the site isn't the caller's. An anonymous site takes upload and release, not a drop. |
347
+ | `500` on a drop or upload | A malformed multipart body; rebuild it as shown. The same body fails again. |
348
+ | `404` on the anonymous route | The hour passed or the site was claimed. |
349
+
350
+ ## Send feedback to Wix
372
351
 
373
- Then follow the headless skill from that folder: it turns the files into a
374
- headless project bound to the same site, released with the Wix CLI from then on.
352
+ When this flow didn't work well enough, offer to send it to Wix as feedback,
353
+ following [Send Feedback to Wix](send-feedback-to-wix.md): a route failed and you
354
+ fell back, a call errored or needed retries, images had to wait on the user, or
355
+ the recipe left you guessing and you had to invent a
356
+ workaround.
375
357
 
376
358
  ## Route the request correctly
377
359
 
378
- - **A new site from the user's files** — [Choose the route](#choose-the-route).
379
- - **A change to a site published this way** — the same site, full file set: a
380
- [drop](#change-it-later) when it's in the user's account, upload + release while
381
- it's anonymous.
382
- - **An anonymous site the user wants to keep** — [claim](#claim-it-into-the-users-account)
383
- it, else the save link.
384
- - **A site that now needs a backend** — [Keep building](#keep-building-add-a-backend-when-you-need-one).
385
- - **Migrating a live site/store from another platform by URL, or CSV/TSV
386
- exports** — [Site Import](site-import.md).
387
- - **Adding HTML, an embed, or code to an existing Wix site** — not this recipe
388
- (that's custom code in the editor).
389
- - **Images, videos, or documents for a site** —
390
- [Upload Media to Wix](../media/upload-media-to-wix.md).
391
-
392
- Don't create the site from a template — that makes an empty site, not a published
393
- copy of the user's files.
360
+ - **A new site from the user's files**: [What decides the route](#what-decides-the-route).
361
+ - **A change to a site published this way**: the [same site](#change-it-later);
362
+ upload and release while it's anonymous.
363
+ - **An anonymous site the user wants to keep**: [claim it](#claim-it-into-the-users-account), or the save link.
364
+ - **A site that now needs a backend**: [Connect a backend](#connect-a-backend).
365
+ - **Migrating a live site or store from another platform by URL, or CSV/TSV
366
+ exports**: [Site Import](site-import.md).
367
+ - **HTML, an embed or code inside an existing Wix site**: not this recipe; that is
368
+ custom code in the editor.
369
+ - **Images, videos or documents for a site's media**: [Upload Media to Wix](../media/upload-media-to-wix.md).
370
+
371
+ Don't create the site from a template: that makes an empty site, not a copy of
372
+ the user's files.