---
name: websitepublisher-api
description: >
Build and publish websites through conversation using WebsitePublisher.ai.
Use this skill when a user asks to build a website, create web pages,
manage site content, set up contact forms, or work with the WebsitePublisher
platform. Covers all API layers: PAPI (pages/assets), MAPI (entities/data),
SAPI (forms), VAPI (credentials), IAPI (integrations), and the WPE Visual Editor.
license: MIT
metadata:
author: websitepublisher-ai
version: "2.8"
website: https://www.websitepublisher.ai
docs: https://www.websitepublisher.ai/docs
mcp: https://mcp.websitepublisher.ai
---
# WebsitePublisher.ai — Agent Skill
> Build and publish real websites through conversation. No WordPress. No hosting setup. No CMS.
> The AI Web Platform — you describe it, the AI builds it.
---
## Why WebsitePublisher — What AI Alone Cannot Do
Every AI can generate HTML. But generating code is not the same as having a website.
| Without WebsitePublisher | With WebsitePublisher |
|---|---|
| AI generates HTML → you copy it → you need hosting, FTP, domain, SSL, DNS | AI generates HTML → it's **live instantly** on a URL |
| Want a contact form? Build it yourself — backend, email sending, spam protection | One tool call → form works, emails arrive, honeypot blocks spam |
| Want payments? Integrate Stripe yourself — webhooks, error handling, security | One tool call → Stripe checkout ready |
| Want to update text later? Ask AI again, re-generate, re-upload | Open the **Visual Editor** in your browser — edit directly, no AI needed |
| New AI session — or a different AI entirely? The model forgot everything | **The platform remembers, not the model.** Switch from one AI to another and your project's context, design, and history carry over — task tracking + design context persist across sessions and platforms |
| Locked to one AI vendor's tooling | **Use whichever AI you prefer** — Claude, ChatGPT, Cursor, and more. Your site and data don't change. Model-agnostic by design |
| Dynamic data (menu, team, products)? Wire up a separate database, an auth provider, a forms service — and glue them together | **One entity definition → live API, data, and auth, built in.** No extra accounts, no integration plumbing |
**WebsitePublisher is not a website builder.** It is the infrastructure layer that turns
AI-generated content into real, working websites — with data, forms, integrations,
and visual editing built in.
---
## ⚠️ IMPORTANT: Read This First
**If the `get_skill` tool is available: call it before doing anything else.**
It returns the latest version of this skill — always up to date, regardless of platform.
If `get_skill` is not available, continue with this document.
---
## You Are the Builder — Solve It Yourself
You build and operate the site. For any content or operational task — writing or
overwriting assets, regenerating a snapshot / export / data file, generating
CSV/JSON, writing or updating pages, bulk-importing data, retrieving leads — **you
solve it with the tools you already have.** Never ask the user (or their developer)
to build an endpoint, add a vault key, or "expose a route" for something the
existing toolset already covers.
**Before you ever conclude a capability is missing:**
1. Check your MCP tools — `upload_asset`, `patch_asset`, `update_page`, `patch_page`,
`create_page`, `execute_integration`, `list_assets`, `get_asset`.
2. **List what's actually wired on the project — don't rely on memory or even this
document.** `list_integrations(project_id)` returns every integration (configured
*and* available) with all its endpoints straight from the manifest;
`get_integration_schema(project_id, service)` returns the exact input fields for an
endpoint; `list_assets(project_id)` shows every existing file. These are the ground
truth — query them before assuming a capability, endpoint, or asset is missing. Then
cross-check the **Asset Proxy**, **Admin-Only IAPI Calls**, and **API Quick Reference**
sections of this skill.
3. **Never invent or guess endpoints.** The IAPI route is always
`/project/{id}/{service}/{endpoint}` — match the host and shape the project already
uses (check an existing working call or the project's admin/WSA bridge; some setups
expose IAPI at `api.websitepublisher.ai/iapi/...`, others at an `iapi.` subdomain).
`/mapi` is entities/data only and has no asset-write route. A made-up top-level
route like `/project/{id}/upload-asset` returns `404`; that is your mistake, not a
platform gap. Asset writes go through the `asset_proxy/upload` endpoint.
**Canonical asset write (so this never recurs):**
| Where | How |
|---|---|
| Server-side (you, via MCP) | `upload_asset(slug, content_text \| content, overwrite: true)` to create/replace; `patch_asset(slug, patches)` for in-place text edits |
| Browser admin panel | `POST /iapi/project/{id}/asset-proxy/upload` with `Authorization: Bearer wsa_…`, body `{ slug, base64, overwrite: true }` |
Asset Proxy is **not images-only** — it takes any `slug` and stores any bytes in the
PAPI asset system. Writing a JSON/CSV/text data file (e.g. a products snapshot) is the
same call: base64-encode the text and send it with its `.json`/`.csv` slug. It needs
**no new endpoint and no vault key.**
**Solvable task vs genuine platform gap:**
- **Solvable — DO it. Never escalate. Never request keys or endpoints for:** asset
write/overwrite · snapshot / export / data-file generation · page write or content
update · bulk import · lead retrieval · admin auth.
- **Genuine platform gap — report it, but do NOT hand off the build.** Only when *no*
MCP tool **and** *no* documented IAPI/PAPI endpoint exists for the operation and it
needs a platform-side code change. Then write **one short paragraph**: the operation,
the tools/endpoints you tried, and why each was insufficient. Do not ask the site's
developer to hand-build a redundant route, and do not request `AAPI_*` or vault keys
to perform content, asset, or export work — those authenticate via `wsa_` or your MCP
session and never need vault AI keys.
---
## Step 1 — Check Connection
Before doing anything, verify the user is connected to WebsitePublisher.ai.
**If connected** (tools respond correctly): proceed to Step 2.
**If not connected**: explain in simple terms:
> "To build your website I need to connect to WebsitePublisher.ai. All you need is your email address — I'll guide you through the rest. It takes about 30 seconds."
Direct the user to sign in at: **https://www.websitepublisher.ai/dashboard**
After signing in, they return here and you continue from Step 2.
---
## Step 2 — Choose the Path
Once connected, ask ONE simple question:
> "What would you like to do? I can build you a brand new site, redesign your existing website, or if you're not sure yet — I can show you what's possible in a few minutes."
### Path A — "Wow Me" (show first, ask later)
The user is curious but not yet convinced. **Do not ask a long list of questions.**
Ask only:
> "What kind of business or project is this for? Just one or two words is fine — like 'restaurant', 'freelance photographer', or 'tech startup'."
Then immediately build a **complete, impressive demo website** based on that one answer. Use your creativity. Make it beautiful. Show what AI can do.
After the demo is live, share the URL and say:
> "Here's what I built in a few minutes. Want to make it yours? I have a few quick questions to personalise it."
Then move to the intake (Path B) — the user is now convinced.
### Path B — Full Intake (user knows what they want)
Ask questions **one at a time**, conversationally. Do not present a form or list.
Speak like a consultant, not a questionnaire. Use simple, friendly language.
**Phase 1 — Goal (start here)**
- What should the website achieve? (get customers, show portfolio, sell something, inform people?)
- Who is the target audience?
- Does a website already exist? If yes: what needs to improve?
**Phase 2 — Identity**
- Business or project name?
- What does the business do? (ask for a short description in their own words)
- Contact details: email, phone, address (only ask what is relevant)
- Logo and brand colours available? (if yes: ask them to share)
**Phase 3 — Style & Technical**
- Three websites they like the look of (not necessarily competitors) — and why?
- Domain name already registered? If yes: which one?
- Any specific pages needed? (about, services, contact, blog, portfolio...)
- Any keywords important for search engines?
**Do not ask all questions if answers make some irrelevant.** A one-page landing page needs far fewer answers than a multi-page business site.
### Path C — Redesign Existing Website
The user has an existing website and wants it improved or migrated to WebsitePublisher.
1. **Ask for the URL** of the existing website
2. **Fetch and analyse** the existing site using a web fetch tool:
- What pages exist?
- What is the content, structure, and messaging?
- What works well? What are the obvious pain points (slow, dated design, poor mobile, unclear CTA)?
3. **Present a short analysis** — 3-5 observations — and propose what you will improve
4. Ask one confirmation question:
> "I'll keep all your content but give it a fresh design with better structure. Any specific things you want to keep or change?"
5. Build the new version — same content, improved design, better UX
6. **Migrate images** — use `upload_asset` with `source_url` to import images from the old site
to the CDN (see "Assets — Importing Images from External URLs" below)
7. Share the URL and point out the specific improvements made
**Do not ask for a long list of preferences before showing something.** Analyse → propose → build → refine.
---
## Design Guidelines
> **Before building any HTML, fetch the design skill for detailed guidelines:**
> Call `get_skill` with `skill_name="design"` — it contains comprehensive typography, color,
> layout, animation, and atmosphere guidelines that produce professional-quality websites.
>
> If a `frontend-design` skill is also available in your environment, read that too.
> The guidelines below are a minimal fallback. The design skill is always more complete.
Every website you build must look **professionally designed**, not like AI-generated template output.
Follow these principles:
### Typography
Choose distinctive, characterful fonts — never default to generic families like Arial, Inter, Roboto, or system fonts.
Pair a display font (for headings) with a refined body font. Google Fonts is available via `` tags.
### Color & Theme
Commit to a cohesive palette with **one dominant color and sharp accents**. Avoid timid, evenly-distributed palettes.
Use CSS custom properties (`--color-primary`, `--color-accent`, etc.) for consistency across pages.
Vary between light and dark themes — do not always default to white backgrounds.
### Layout & Composition
Break out of predictable grid patterns. Use asymmetry, generous whitespace, overlapping elements, or full-bleed sections to create visual interest.
Every page should have a clear visual hierarchy that guides the visitor's eye.
### Motion & Micro-interactions
Add CSS animations for page load reveals (staggered `animation-delay`), hover state transitions, and scroll-triggered effects.
Prioritize CSS-only solutions. One well-orchestrated entrance animation creates more impact than scattered effects.
### Atmosphere
Create depth and texture — not flat solid-color blocks. Use gradient meshes, subtle noise/grain overlays, layered transparencies, or dramatic shadows depending on the aesthetic.
### The Rule
**No two websites should look the same.** Match the design to the business, audience, and purpose.
A law firm looks nothing like a skate shop. A restaurant looks nothing like a SaaS landing page.
If the user has not specified a style preference, choose a bold direction and commit to it.
---
## Step 3 — Build the Website
### Project Setup
1. Get available projects: use `list_projects`
2. If no project exists or user wants a new one: use `create_project` with a name (and optional subdomain)
3. Note the `project_id` — used in every subsequent call
4. **Check design context:** call `get_project_status` — if `design_context` is set, use those colors, fonts, and style notes as the foundation for all pages you build. If `design_context` is null, ask the user for their preferred colors, fonts, and style direction during intake, then save it:
```
execute_integration(
project_id: ...,
service: "site_context",
endpoint: "set-context",
input: {
color_palette: { primary: "#...", secondary: "#...", accent: "#...", background: "#...", text: "#..." },
fonts: { heading: "Font Name", body: "Font Name" },
style_notes: "Short description of the visual direction",
locale: "en"
}
)
```
This ensures all future sessions automatically match the same design language.
### Page Structure Guidelines
Plan the pages before building. Common structures:
| Website type | Recommended pages |
|---|---|
| Landing page | index only |
| Business / SME | index, about, services, contact |
| Portfolio | index, work/projects, about, contact |
| Restaurant | index, menu, about, reservations/contact |
| Blog | index, blog-overview, post-template, about |
Always create an `index` page first — this becomes the homepage.
### Fragments — Reusable Components Across Pages
When a website has more than one page, shared elements like headers, footers, and
navigation **must** be built as fragments — not copied between pages.
**What is a fragment?** A reusable HTML snippet stored once and included in any page.
When you update the fragment, every page that uses it updates automatically.
**When to use fragments:**
- Navigation / header — always
- Footer — always
- Any section that appears on 2+ pages (CTA banner, sidebar, cookie notice)
**How to create and use fragments:**
1. Create the fragment:
```
create_fragment(
project_id: 12345,
name: "site-header",
content: "
```
**After upload via the editor** the `src` is automatically replaced by the CDN URL
(`cdn.websitepublisher.ai/custom/wid{id}/images/...`).
#### Common placeholder dimensions
| Usage | Dimensions |
|---|---|
| Hero wide | 1200x675 |
| Photo 4:3 | 800x600 |
| Portrait | 600x800 |
| Nav logo | 240x48 |
| Team card | 600x520 |
#### ⚠️ Image performance — mandatory rules
Every `
```
**Impact:** A page with 11 images missing `loading="lazy"` fires 11 simultaneous CDN requests on page load. On mobile (slower network, in-app mail browsers), this causes blank pages and multi-second load delays. Adding lazy loading reduced this to 1-2 eager requests with the rest deferred.
### Assets — Images, CSS, JS, and Files
Assets are files stored on the WebsitePublisher CDN (`cdn.websitepublisher.ai/custom/wid{id}/...`).
Use `upload_asset` to add images, stylesheets, JavaScript, fonts, PDFs, and other static files
to a project. Assets are served globally with caching — fast and reliable.
**Three ways to provide content:**
| Parameter | Use for | Example |
|---|---|---|
| `source_url` | Import from any public URL — the server fetches it for you | Images from existing websites, Unsplash, DALL·E, any HTTPS URL |
| `content` | Base64-encoded binary data | Images generated locally or received as base64 |
| `content_text` | Plain text content (saves tokens vs base64) | CSS, JS, JSON, SVG, HTML, XML, MD files |
Always provide exactly **one** of the three. Never combine them.
#### Importing Images from External URLs
The `source_url` parameter is the easiest way to bring images into a project. The server
fetches the file, validates it (HTTPS only, no internal IPs), and stores it on the CDN.
This works for **any public HTTPS URL** — not limited to any specific platform.
**Common use cases:**
- Migrating images from an existing website (WordPress, Wix, Squarespace, any CMS)
- Importing stock photos from Unsplash, Pexels, or similar services
- Saving AI-generated images (DALL·E, Midjourney URLs)
- Pulling logos or assets from a client's current hosting
**Example — import a single image:**
```
upload_asset(
project_id: 12345,
slug: "images/hero-photo.jpg",
source_url: "https://existing-site.com/wp-content/uploads/2025/hero.jpg"
)
→ CDN URL: cdn.websitepublisher.ai/custom/wid12345/images/hero-photo.jpg
```
**Example — batch import from an existing site:**
```
upload_asset(project_id: 12345, slug: "images/project-1.jpg", source_url: "https://old-site.nl/uploads/photo1.jpg")
upload_asset(project_id: 12345, slug: "images/project-2.jpg", source_url: "https://old-site.nl/uploads/photo2.jpg")
upload_asset(project_id: 12345, slug: "images/team-photo.jpg", source_url: "https://old-site.nl/uploads/team.jpg")
```
Then reference the new CDN URLs in your page HTML:
```html
```
**Rules:**
- `source_url` must be HTTPS — HTTP URLs are rejected
- Internal/private IP addresses are blocked (SSRF protection)
- Works for images (JPEG, PNG, WebP, GIF), PDF, fonts (.woff, .woff2, .ttf), and .ico files
- Set `overwrite: true` to replace an existing asset with the same slug
- The slug determines the CDN path — use descriptive names: `images/hero.jpg`, `images/team/jan.jpg`
- Alt text can be set via the `alt` parameter for images
**When migrating a website:** list all images on the old site first (via web fetch, sitemap,
or CMS tools), then upload each one with `source_url`. Update page HTML to reference the
new CDN URLs. The old site must remain accessible until all images have been imported.
#### Uploading Text-Based Assets
For CSS, JavaScript, JSON, SVG, and other text files, use `content_text` instead of base64
encoding. This is more token-efficient and easier to read:
```
upload_asset(
project_id: 12345,
slug: "//cdn.websumo.com/css/custom-styles.css",
content_text: "body { font-family: 'Inter', sans-serif; }"
)
```
#### Managing Existing Assets
| Action | Tool |
|---|---|
| List all assets | `list_assets(project_id: 12345)` |
| Read asset content | `get_asset(project_id: 12345, slug: "//cdn.websumo.com/js/app.js")` |
| Edit text asset in place | `patch_asset(project_id: 12345, slug: "//cdn.websumo.com/js/app.js", patches: [...])` |
| Replace asset | `upload_asset(project_id: 12345, slug: "images/old.jpg", source_url: "...", overwrite: true)` |
| Delete asset | `delete_asset(project_id: 12345, slug: "images/unused.jpg")` |
### Dynamic Data (MAPI) — When Entities Make Sense
**Use MAPI entities when content is managed independently of page design** — the
owner (or a different AI session) should be able to add, remove, or reorder items
without touching page HTML.
**Use MAPI + SSR for:**
| Content type | Entity name | Example fields |
|---|---|---|
| Menu items | `menuitems` | name, description, price, category, sort_order |
| Team members | `team` | name, role, bio, photo_url, sort_order |
| Services / offerings | `services` | title, description, icon, price, sort_order |
| Portfolio projects | `projects` | title, description, image_url, link, category, sort_order |
| Testimonials / reviews | `testimonials` | name, role, company, quote, photo_url |
| Blog posts | `posts` | title, slug, content, author, published_at, featured_image |
| FAQ items | `faq` | question, answer, category, sort_order |
| Events | `events` | title, date, location, description, registration_url |
| Products (showcase) | `products` | name, description, price, image_url, category |
**Use static HTML when:**
- Content is small and fixed (≤5 items that rarely change — e.g. 3 services on an about page)
- The page is a one-off (hero text, about narrative, single landing page)
- The owner will only update content through an AI session anyway
- It's page structure and layout (sections, containers)
**Don't over-engineer.** A restaurant with 8 menu items that change twice a year
does not need a MAPI entity + SSR template + admin panel. Static HTML with clear
structure is fine — the AI can update it in 30 seconds when the menu changes.
**The trigger for MAPI:** when you hear "I want to add/remove items myself" or when
items will grow beyond 10, or when multiple pages show the same data differently
(e.g. a shop overview AND a homepage featured section both pulling from products).
**How to build with MAPI:**
1. Define the entity:
```
create_entity(project_id: 12345, name: "services", plural_name: "services",
properties: [
{ name: "title", type: "string", required: true },
{ name: "description", type: "text" },
{ name: "icon", type: "string" },
{ name: "price", type: "string" },
{ name: "sort_order", type: "number" }
],
public_read: true
)
```
2. Create records:
```
create_record(project_id: 12345, entity: "services", data: {
title: "Web Design", description: "...", icon: "🎨", price: "From €499", sort_order: 1
})
```
3. **Render with SSR (preferred — SEO-friendly):**
Use `
```
This is the **default choice** for rendering MAPI data. Always use SSR unless the page
needs interactive features like client-side search, filtering, or live updates.
4. Render with JavaScript (only when interactivity is needed):
Use client-side `fetch()` when the user needs to search, filter, or sort dynamically
**in the browser**. SSR and JS can coexist on the same page.
```javascript
fetch('https://api.websitepublisher.ai/mapi/public/{project_id}/services')
.then(r => r.json())
.then(data => {
const container = document.getElementById('services-grid');
data.data
.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0))
.forEach(service => {
container.innerHTML += `
${service.description}
From: {{fields.name}} ({{fields.email}})
{{fields.message}}
" } }, max_submits_per_session: 5 ) ``` ### Step 2 — Add the CDN script + form handler to the page **Always use the CDN library.** Do not write inline session management code. The library handles sessions, CSRF tokens, stale session recovery, and all headers automatically. > ⚠️ **`WP.sapi()` is for visitor-facing forms only** — contact forms, file uploads, > member-area magic links. **Do not use it for admin authentication.** Admin login and > admin-only IAPI calls use direct `fetch()` to `/iapi/project/{id}/admin-auth/...` with > `Authorization: Bearer` headers — see the **Admin-Protected Pages** section below. ```html ``` ### What the CDN library handles for you | Feature | How | |---|---| | Session creation + resume | `WP.sapi(PROJECT_ID)` pre-warms on init | | CSRF token management | Sent via `X-CSRF-Token` header + `_csrf` body (dual) | | Session ID header | `X-Session-Id` header on every request | | Stale session recovery | 401 response -> auto-clear -> fresh session -> retry (max 1) | | Per-project storage keys | `wp_{projectId}_sid` -- no cross-site conflicts | | Safari ITP compatibility | Uses sessionStorage (first-party, never blocked) | | Auth state preservation | After successful POST, only CSRF is cleared -- session ID survives | ### Key rules -- never forget these: | Rule | Why | |---|---| | Always include `website: ''` in the fields object | Honeypot field -- bots fill it in, humans leave it empty. Server silently drops the submission if non-empty | | Never pre-fill the honeypot field | An empty string is required -- any value triggers bot detection | | Replace `PROJECT_ID` with the actual numeric project ID | The library uses this to scope sessions and build API URLs | ### Forms with File Upload Forms can accept image uploads from visitors via the SAPI upload endpoint. Uploads are stored as project assets on the CDN -- no bearer token needed. > **Building an admin panel with image upload?** See "Image Upload in Admin Panels" > under the Admin-Protected Pages section — it shows how to combine admin auth > with SAPI upload on the same page. **Flow:** upload file(s) first -> collect CDN URLs -> include in form submit fields. ```javascript // Upload a file using the CDN library var sapi = WP.sapi(PROJECT_ID); async function uploadFile(file) { // getSession() handles caching + resume automatically var session = await sapi.getSession(); var form = new FormData(); form.append('file', file); form.append('_csrf', session.csrf_token); form.append('form_name', 'intake'); var res = await fetch( 'https://api.websitepublisher.ai/sapi/project/' + PROJECT_ID + '/form/upload', { method: 'POST', headers: { 'X-Session-Id': session.session_id }, body: form // no Content-Type header -- browser sets multipart boundary } ); var data = await res.json(); if (data.success) { // Clear stored CSRF -- the library will fetch a fresh one on next call sapi.clearSession(); return data.data.asset_url; // CDN URL ready for use } throw new Error(data.error && data.error.message || 'Upload failed'); } ``` **Upload rules:** | Rule | Value | |---|---| | Allowed types | JPEG, PNG, WebP only | | Max file size | 5 MB per file | | Max per session | 10 uploads | | CSRF | Single-use -- library handles refresh automatically for submitForm(), manual clear needed after raw fetch upload | | Response includes | `asset_url`, `filename`, `mime_type`, `size`, `width`, `height`, `uploads_remaining` | **Include uploaded URLs in form submit:** ```javascript // After uploading, pass CDN URLs as regular form fields sapi.submitForm('intake', { name: '...', email: '...', image_url_1: uploadedUrl1, // CDN URL from upload response image_url_2: uploadedUrl2, website: '', // honeypot }); ``` --- ## Step 4 — Go Live Checklist Before handing over to the user, verify: - [ ] Homepage has `landingpage: true` (or was created first) - [ ] All pages that should be findable have `seo_robots_index: true` - [ ] All pages have `seo_title` and `seo_description` - [ ] All `` comment tags are present in every page - [ ] Multi-page sites use **fragments** for header and footer (not copy-pasted HTML) - [ ] Repeating content uses **MAPI entities** (not hardcoded static HTML) - [ ] Contact form (if any) uses the CDN library (`sapi-client.js`) — no inline session code - [ ] Thank-you page exists if form redirects after submit - [ ] Terms / privacy page exists if form collects personal data - [ ] Design uses distinctive typography and cohesive color palette (not generic AI defaults) - [ ] Design context saved via `execute_integration(service: "site_context")` for future consistency - [ ] Website URL shared with user: `https://{subdomain}.websitepublisher.ai` - [ ] Contact form includes `website: ''` honeypot field in the fields object - [ ] Visual Editor session offered for image replacement and final tweaks --- ## Platform Knowledge ### What WebsitePublisher handles automatically | Feature | How it works | |---|---| | **Sitemap** | Auto-generated. Pages appear when `seo_robots_index: true` | | **robots.txt** | Auto-generated with "Allow all" + sitemap reference | | **SSL certificate** | Auto-provisioned via Let's Encrypt on custom domains | | **Canonical tags** | Injected by Optimizer when comment tag is present | | **Open Graph** | Injected by Optimizer (uses SEO title/description) | | **Static caching** | Pages are served as static files — extremely fast | | **CDN** | Assets served via cdn.websitepublisher.ai | ### What requires API calls | Feature | API | |---|---| | Pages and content | PAPI | | Reusable components (header, footer) | PAPI Fragments | | Dynamic data / entities | MAPI | | Contact forms | SAPI | | Third-party integrations | IAPI + VAPI | | Visual editing (browser) | WPE | | Clone a website | WAPI clone endpoint | --- ## Built-in Integrations — Composable Building Blocks WebsitePublisher's integrations are not a feature list — they are composable building blocks. Every integration speaks the same interface (`execute_integration(service, endpoint, input)`), authenticates the same way (the Vault), and is callable from any AI on any platform via MCP. This means the AI doesn't *build* a payment flow, an email pipeline, or a lead system — it *assembles* them from pieces that are already wired, secured, and maintained. Generating code gives you a draft; snapping integrations together gives you a working system. ### Don't reinvent the wheel WebsitePublisher includes pre-built integrations for common website needs. You do not need to build email sending, payment processing, or SMS from scratch. Each integration is a single tool call — credentials are stored securely in the Vault, the platform handles authentication, rate limiting, and error handling. ### Discover what's available — list it, don't guess You never have to guess which integrations or endpoints exist. Two tools read the **live manifest** for the current project — they are the source of truth, more current than this document: - `list_integrations(project_id)` — every integration, split into **configured** (vault secrets present, ready to call now) and **available** (needs setup), each with its full endpoint list and descriptions. A typical project already has dozens wired (asset upload, exports, payments, email, shipping, imports, analytics, and more). - `get_integration_schema(project_id, service)` — the exact input fields (name, required, type, limits) for every endpoint of one integration. Call this before `execute_integration` so you send the correct body the first time. If a task seems to need a capability you have no tool for, run `list_integrations` **first**. The endpoint almost always already exists. Inventing an HTTP route, guessing a hostname, or asking the user to build an endpoint is the wrong move — the manifest already tells you what is there and how to call it. ### Available Integrations | Service | Category | What it does | Tool call | |---|---|---|---| | **Resend** | Email | Transactional emails (contact forms, notifications) | `execute_integration(service: "resend", endpoint: "send-email")` | | **Mailgun** | Email | Domain-level email sending | `execute_integration(service: "mailgun", endpoint: "send-email")` | | **SendGrid** | Email | High-volume email delivery | `execute_integration(service: "sendgrid", endpoint: "send-email")` | | **Stripe** | Payments | Checkout sessions, payment processing | `execute_integration(service: "stripe", endpoint: "create-checkout-session")` | | **Mollie** | Payments | European payments (iDEAL, Bancontact, cards) | `execute_integration(service: "mollie", endpoint: "create-payment")` | | **Twilio** | SMS | Text messages (confirmations, alerts) | `execute_integration(service: "twilio", endpoint: "send-sms")` | | **Lead Capture** | Built-in | Store form submissions as leads | Form action `{"type": "leads"}` | | **Admin Auth** | Built-in | Password-protected admin areas (email/password login, Bearer token auth) | `execute_integration(service: "admin_auth", endpoint: "login")` | | **Auth Keys** | Built-in | Request project API keys stored in vault (human-approved) | `execute_integration(service: "auth_keys", endpoint: "request-key")` | | **Asset Proxy** | Built-in | Upload/delete assets from browser admin panels (no WPA key needed) | `execute_integration(service: "asset-proxy", endpoint: "upload")` | | **Site Context** | Built-in | Store design decisions across sessions | `execute_integration(service: "site_context", endpoint: "set-context")` | | **Product Catalog** | Built-in | Products, variants, categories, bulk import | `execute_integration(service: "product-catalog", endpoint: "list-products")` | | **Request Tracer** | Built-in | Debug API + page requests in real-time | `execute_integration(service: "tracer", endpoint: "start")` | ### How integrations work 1. **Setup** — Store the API key: `setup_integration(service: "resend", secrets: {"resend_api_key": "re_..."})` 2. **Use** — Call the integration: `execute_integration(service: "resend", endpoint: "send-email", input: {...})` 3. **Done** — The platform resolves credentials, validates input, proxies the request, returns the result API keys are **never exposed** to the AI or the browser. The Vault encrypts them at rest and the integration proxy resolves them server-side at execution time. ### Vault References — `{{vault:key_name}}` The IAPI proxy resolves `{{vault:key_name}}` server-side before making API calls. This is the core security mechanism that keeps secrets out of AI conversations and browser code. **Where vault references work (server-side only):** | Context | Works? | Example | |---|---|---| | `execute_integration` input | ✅ | `"api_key": "{{vault:stripe_key}}"` | | Scheduled tasks (AAPI) | ✅ | Vault refs in task payload resolved at execution | | IAPI proxy calls | ✅ | Bearer token from vault | | Browser JavaScript | ❌ | Browser cannot access vault — use admin auth (`wsa_`) instead | | Page HTML source | ❌ | Would expose secrets to anyone viewing source | | MCP tool responses | ❌ | VaultSanitizer strips any leaked vault values | **Critical rule:** Never put vault keys in browser-facing code. If a browser page needs to call an authenticated API, use the **admin auth pattern** (`wsa_` token) for data operations and **SAPI upload** for file uploads. The vault exists for server-side integrations only. ### When to use integrations | User wants... | Use this | |---|---| | Contact form that sends email | SAPI form + Resend integration | | Accept payments on website | Stripe or Mollie integration | | SMS confirmation after booking | Twilio integration | | Store leads from multiple forms | Built-in Lead Capture | | Password-protected admin dashboard | Admin Auth (IAPI admin session) | | Member area with magic link / code login | SAPI Visitor Auth | | Remember design choices across sessions | Site Context integration | | Import 50-500 products at once | `bulk-upsert-products` (Product Catalog) | | Upload images from admin panel (browser) | **Asset Proxy** (PAPI assets) or **SAPI upload** (form uploads) | | Request a project API key securely | Auth Keys (human-approved, vault-stored) | | Debug failing requests or slow pages | Request Tracer | **Always check if an integration exists before building custom solutions.** The built-in integrations handle authentication, error handling, rate limiting, and security — reimplementing these is unnecessary and error-prone. ### Bulk Product Import For large catalogs, use `bulk-upsert-products` instead of looping `create-product`: ``` execute_integration( service: "product-catalog", endpoint: "bulk-upsert-products", input: { "items": [ {"sku": "TSH-001", "name": "Classic Tee", "price_cents": 2999, "status": "active"}, {"sku": "TSH-002", "name": "V-Neck Tee", "price_cents": 3499, "status": "active"}, {"sku": "TSH-001", "price_cents": 2799} ] } ) ``` Each item is matched by SKU: existing → update, new → create (needs `name` + `price_cents`). Max 500 items per call. Response includes per-item status and `summary.by_error_type`. **Always check `result.failed` and `result.summary.by_error_type`** — `success: true` means the call itself worked, not that every item succeeded. ### Debugging with Request Tracer When something isn't working — a page returns wrong data, an integration fails, or performance is slow — use the Request Tracer to see exactly what happened: 1. **Start a trace session:** ``` execute_integration( service: "tracer", endpoint: "start", input: { "ttl": 120, "include_optimizer": true } ) → returns hash (e.g., "tr_abc12345") ``` 2. **Perform the operation that's failing** — create a page, submit a form, call an integration 3. **Read the trace:** ``` execute_integration( service: "tracer", endpoint: "logs", input: { "hash": "tr_abc12345" } ) ``` The trace shows every API request and page render with HTTP method, path, status code, duration, SQL query summary, and which server handled the request. Integration failures include typed error data (`error_type`, `error_code`, `error_field`, `recovery`) so you can see exactly what went wrong without guessing. **When to use the tracer:** - Page renders wrong content → trace optimizer request, check SQL queries - Integration call fails → trace API request, check `error_type` and `recovery` - Request is slow → check `duration_ms` and `db.total_ms` breakdown - "It works sometimes" → `server` field shows which node handled each request **Options:** - `include_optimizer: true` — also trace public page renders (default: off) - `include_sql: false` — skip SQL summary (default: on) - `ttl: 10-300` — session duration in seconds (default: 60) --- ## Admin-Protected Pages — IAPI Admin Auth > **⚠️ Need to upload images from an admin panel?** Do NOT use `upload_asset`, > vault keys, or MAPI asset routes from the browser. Use **Asset Proxy** > (`/iapi/project/{id}/asset-proxy/upload` with your `wsa_` admin token) — it's > the simplest option. See "Image Upload in Admin Panels" below. When building dashboards, admin panels, or any page that requires a logged-in admin (not a public visitor), use the IAPI Admin Auth pattern. This is separate from SAPI Visitor Auth — they serve different purposes. | Feature | Admin Auth (IAPI) | Visitor Auth (SAPI) | |---|---|---| | **Use case** | Admin dashboards, CMS, internal tools | Member areas, gated content, loyalty portals | | **Login method** | Email + password | Magic link or verification code | | **Token storage** | `sessionStorage.admin_token` | Managed by sapi-client.js internally | | **API calls** | Direct `fetch()` to `/iapi/project/{id}/...` with `Authorization: Bearer` | `WP.sapi(id).call(...)` via CDN library | | **Token prefix** | `wsa_` (server-side) | Session ID (no token exposed to page) | ### Admin Login ```javascript const PROJECT_ID = 12345; // replace with actual project ID async function login(email, password) { const r = await fetch(`/iapi/project/${PROJECT_ID}/admin-auth/login`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({email, password}) }); const data = await r.json(); if (data.success && data.token) { sessionStorage.setItem('admin_token', data.token); localStorage.setItem('admin_token', data.token); document.cookie = `admin_token=${data.token}; path=/; max-age=28800; SameSite=Lax`; } return data; } ``` Triple storage (sessionStorage + localStorage + cookie) ensures the token survives page navigations, tab reopens, and server-side middleware checks. After login, redirect to **`/`** if your dashboard page is set as `landingpage: true`. See the note about `landingpage` under Page Metadata. ### Admin-Only IAPI Calls ```javascript async function callAdmin(service, endpoint, payload) { const token = sessionStorage.getItem('admin_token'); if (!token) { window.location.replace('/login'); return; } const r = await fetch(`/iapi/project/${PROJECT_ID}/${service}/${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }, body: JSON.stringify(payload) }); if (r.status === 401) { sessionStorage.removeItem('admin_token'); localStorage.removeItem('admin_token'); window.location.replace('/login'); return; } return r.json(); } // Usage: const leads = await callAdmin('leads', 'get-leads', { page: 1, per_page: 25 }); const msg = await callAdmin('anthropic', 'create-message', { prompt: '...' }); ``` **Important:** Use direct `fetch()` — not `WP.sapi().call()`. The SAPI client library is for visitor sessions. Admin calls use `Authorization: Bearer` headers on `/iapi/` routes. ### Page Rendering — Auth Guard ```html