Build and publish real websites through conversation. You describe it — the AI builds and deploys it.
The user's credentials are set as environment variables:
WEBSITEPUBLISHER_TOKEN — API token (starts with wpa_)WEBSITEPUBLISHER_PROJECT — Project ID (numeric, e.g. 12345)If either variable is missing, stop and instruct the user:
Sign up at https://www.websitepublisher.ai/dashboard
Copy their API token and Project ID from the dashboard
Add to their OpenClaw config: json
{
"env": {
"WEBSITEPUBLISHER_TOKEN": "wpa_xxxx...",
"WEBSITEPUBLISHER_PROJECT": "12345"
}
}
Every API call requires these headers:
text
Authorization: Bearer $WEBSITEPUBLISHER_TOKEN
Content-Type: application/json
Accept: application/json
Base URL: https://api.websitepublisher.ai
When asked to build a website, follow this exact sequence:
bash
curl -s \
-H "Authorization: Bearer $WEBSITEPUBLISHER_TOKEN" \
-H "Accept: application/json" \
"https://api.websitepublisher.ai/papi/project/$WEBSITEPUBLISHER_PROJECT/status"
This returns the project URL, existing pages, and current status. Always do this first.
Single page:
bash
curl -s -X POST \
-H "Authorization: Bearer $WEBSITEPUBLISHER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"slug": "index.html",
"content": "<!DOCTYPE html><html lang=\"en\">...</html>",
"title": "Homepage",
"seotitle": "Company Name — Tagline",
"seodescription": "One sentence describing the page for search engines."
}' \
"https://api.websitepublisher.ai/papi/project/$WEBSITEPUBLISHER_PROJECT/pages"
Multiple pages at once (preferred — saves API calls):
bash
curl -s -X POST \
-H "Authorization: Bearer $WEBSITEPUBLISHER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"pages": [
{"slug": "index.html", "content": "...", "title": "Home"},
{"slug": "about.html", "content": "...", "title": "About"},
{"slug": "contact.html", "content": "...", "title": "Contact"}
]
}' \
"https://api.websitepublisher.ai/papi/project/$WEBSITEPUBLISHER_PROJECT/pages/bulk"
bash
curl -s -X POST \
-H "Authorization: Bearer $WEBSITEPUBLISHER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"slug": "images/logo.png",
"content": "BASE64_ENCODED_DATA",
"alt": "Company Logo"
}' \
"https://api.websitepublisher.ai/papi/project/$WEBSITEPUBLISHER_PROJECT/assets"
Asset URL pattern: https://cdn.websitepublisher.ai/[path]/[slug]
Reference assets in HTML using this CDN URL.
The project URL is returned by the status endpoint. Share it with the user.
ActionMethodEndpointList pagesGET/papi/project/{id}/pagesGet pageGET/papi/project/{id}/pages/{slug}Create pagePOST/papi/project/{id}/pagesBulk createPOST/papi/project/{id}/pages/bulkUpdate pagePUT/papi/project/{id}/pages/{slug}Patch pagePATCH/papi/project/{id}/pages/{slug}Delete pageDELETE/papi/project/{id}/pages/{slug}List versionsGET/papi/project/{id}/pages/{slug}/versionsRollbackPOST/papi/project/{id}/pages/{slug}/rollback
Page fields:
json
{
"slug": "about.html",
"content": "full HTML string",
"title": "label shown in dashboard",
"seotitle": "browser tab + search title",
"seodescription": "meta description for search",
"seokeywords": "comma,separated,keywords",
"canonical": "https://example.com/about"
}
Patch a page (partial update — send only changed HTML fragment):
bash
curl -s -X PATCH \
-H "Authorization: Bearer $WEBSITEPUBLISHER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"patches": [
{
"search": "<h1>Old Title</h1>",
"replace": "<h1>New Title</h1>"
}
]
}' \
"https://api.websitepublisher.ai/papi/project/$WEBSITEPUBLISHER_PROJECT/pages/index.html"
ActionMethodEndpointList assetsGET/papi/project/{id}/assetsGet assetGET/papi/project/{id}/assets/{slug}Upload assetPOST/papi/project/{id}/assetsBulk uploadPOST/papi/project/{id}/assets/bulkDelete assetDELETE/papi/project/{id}/assets/{slug}
Asset fields:
json
{
"slug": "images/hero.jpg",
"content": "BASE64_STRING or https://example.com/image.jpg",
"alt": "Description for accessibility",
"type": "image"
}
Tip: You can pass a public image URL as
content— the platform fetches and stores it automatically.
Use MAPI when the website needs structured, repeatable data — product listings, blog posts, team members, portfolio items, etc.
bash
curl -s -X POST \
-H "Authorization: Bearer $WEBSITEPUBLISHER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "products",
"properties": [
{"name": "name", "type": "string", "required": true},
{"name": "price", "type": "number"},
{"name": "description", "type": "text"},
{"name": "image", "type": "image"}
]
}' \
"https://api.websitepublisher.ai/mapi/project/$WEBSITEPUBLISHER_PROJECT/entities"
bash
curl -s -X POST \
-H "Authorization: Bearer $WEBSITEPUBLISHER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"name": "Wireless Headphones",
"price": 89.99,
"description": "Premium noise-cancelling headphones",
"image": "https://example.com/headphones.jpg"
}' \
"https://api.websitepublisher.ai/mapi/project/$WEBSITEPUBLISHER_PROJECT/products"
Note: MAPI endpoint uses the plural entity name — e.g.
/productsnot/product.
ActionMethodEndpointList entitiesGET/mapi/project/{id}/entitiesCreate entityPOST/mapi/project/{id}/entitiesGet entity schemaGET/mapi/project/{id}/entities/{name}/propertiesList recordsGET/mapi/project/{id}/{entity}Get recordGET/mapi/project/{id}/{entity}/{recordId}Create recordPOST/mapi/project/{id}/{entity}Update recordPUT/mapi/project/{id}/{entity}/{recordId}Delete recordDELETE/mapi/project/{id}/{entity}/{recordId}
Public read: MAPI records are publicly readable at the same endpoint without a token — useful for frontend JavaScript fetching live data.
Add a working contact form to any page in two steps.
bash
curl -s -X POST \
-H "Authorization: Bearer $WEBSITEPUBLISHER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"page_slug": "contact.html",
"fields": [
{"name": "name", "type": "text", "required": true, "label": "Your Name"},
{"name": "email", "type": "email", "required": true, "label": "Email Address"},
{"name": "message", "type": "textarea", "required": true, "label": "Message"}
],
"submit_label": "Send Message",
"success_message": "Thank you! We will get back to you shortly.",
"notification_email": "hello@example.com"
}' \
"https://api.websitepublisher.ai/sapi/project/$WEBSITEPUBLISHER_PROJECT/forms"
Important: Always send ALL fields on every configure_form call — no partial updates.
The API returns a form_id. Use this snippet in the page HTML:
html
<form id="wp-contact-form" data-form-id="FORM_ID_HERE">
<!-- fields rendered by JS -->
</form>
<script src="https://api.websitepublisher.ai/sapi/form.js"></script>
ActionMethodEndpointConfigure formPOST/sapi/project/{id}/formsList formsGET/sapi/project/{id}/formsRemove formDELETE/sapi/project/{id}/forms/{form_id}
PlanLimitFree30 requests/minStarter60 requests/minPro+150-300 requests/min
Always prefer bulk endpoints ( /pages/bulk, /assets/bulk) over individual calls to stay within rate limits. A bulk call counts as one request regardless of how many items it creates.
GET /status — check projectPOST /pages — create index.html with full contentPOST /forms — configure contact form if neededGET /status — check projectPOST /pages/bulk — create all pages in one callPOST /assets/bulk — upload all images in one callPOST /forms — configure contact form on contact pageGET /status — check projectPOST /entities — create data model (e.g. "products")POST /{entity} — create records (one per product)POST /pages/bulk — create pages with JS that fetches MAPI dataAll endpoints return standard HTTP status codes:
CodeMeaning200/201Success400Invalid request — check your JSON fields401Invalid or missing token404Page/asset/entity not found422Validation error — response body contains field errors429Rate limit exceeded — use bulk endpoints or wait500Server error — try again
On error, the response body always contains a message field with a description.