PAPI Documentation
The Pages & Assets API (PAPI) lets you create, manage, and publish web pages, reusable fragments, and assets programmatically. It includes versioning, diff-patch, tracking script injection, and SEO metadata.
Authentication
All PAPI requests require authentication via Bearer token in the Authorization header:
Authorization: Bearer {your_token}
Three token types are supported:
| Token Prefix | Scope | Description |
|---|---|---|
wps_... | Project | Scoped to a single project |
wpa_... | Account | Access all projects owned by the account |
OAuth | User | Issued via OAuth 2.1 + PKCE (MCP, GPT Actions) |
wps_), the project ID in the URL must match the token's project.
Base URL
https://api.websitepublisher.ai/papi
All PAPI endpoints use the /papi prefix. The full API gateway provides:
| API | Base Path | Purpose |
|---|---|---|
| PAPI | /papi/* | Pages, assets, fragments, tracking |
| MAPI | /mapi/* | Dynamic data & entities |
| SAPI | /sapi/* | Sessions, forms, visitor auth |
| VAPI | /vapi/* | Vault & secrets |
| IAPI | /iapi/* | Integrations |
| DAPI | /dapi/* | Dashboard |
Project Status
Get the current status of a project including page, asset, and entity counts.
Response
{
"success": true,
"data": {
"project_id": 12345,
"domain": "mysite.websitepublisher.ai",
"pages_count": 5,
"assets_count": 12,
"status": "published"
}
}
Pages
List all pages in the project. Add ?type=page to exclude fragments, or ?type=fragment to list only fragments.
Create a new page.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
slug | string | Yes | URL path (e.g., "about", "blog/post1") |
content | string | Yes | Full HTML content starting with <!DOCTYPE html> |
meta | object | No | Page metadata (title, language, redirects — see Page Metadata) |
seo | object | No | SEO settings (description, robots, keywords) |
Example Request
{
"slug": "about",
"content": "<!DOCTYPE html><html>...</html>",
"meta": {
"title": "About Us",
"language": "en"
},
"seo": {
"description": "Learn more about our company",
"robots_index": true,
"robots_follow": true
}
}
Get a specific page by slug. Returns the source HTML (with include-tags intact for pages using fragments).
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
include_version | boolean | false | Include version number and version_hash in the response. Required for PATCH operations. |
Example Response (with include_version=true)
{
"success": true,
"data": {
"slug": "about",
"content": "<!DOCTYPE html>...",
"version": 3,
"version_hash": "a3f8c2d1",
"updated_at": "2026-02-13T14:00:00Z"
}
}
Replace an existing page with new content. Creates a new version. The slug cannot be changed — to move a page, delete it and create a new one.
Delete a page and all its version history.
Diff-Patch
Apply targeted find-and-replace changes to a page without sending the full content. Uses optimistic locking via base_version_hash to prevent conflicts. Much more efficient than a full PUT for small edits.
Apply one or more patch operations to a page.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
base_version_hash | string | Yes | Hash from GET ?include_version=true. Prevents overwrites if page changed. |
patches | array | Yes | Array of patch operations (see below) |
patch_summary | string | No | Human-readable description of the change (stored in version history) |
Patch Operations
| Field | Type | Description |
|---|---|---|
operation | string | "replace" or "delete" |
find | string | Exact text to search for (must exist exactly once in current content) |
replace | string | Text to replace it with (required for "replace", omit for "delete") |
Example — Replace
{
"base_version_hash": "a3f8c2d1",
"patches": [
{
"operation": "replace",
"find": "<h1>Old Title</h1>",
"replace": "<h1>New Title</h1>"
}
],
"patch_summary": "Updated page title"
}
Example — Delete
{
"base_version_hash": "a3f8c2d1",
"patches": [
{
"operation": "delete",
"find": "<div class=\"banner\">...</div>"
}
],
"patch_summary": "Removed promotional banner"
}
Success Response
{
"success": true,
"data": {
"slug": "about",
"version": 4,
"version_hash": "jkl012mn",
"patches_applied": 1,
"content_preview": "<!DOCTYPE html>...(first 500 chars)"
}
}
VERSION_CONFLICT error is returned. Fetch the latest version with ?include_version=true and retry.
Versioning
Every page update (PUT, PATCH, rollback) creates a new version. Versions are forward-only — rollbacks create a new version with the content of the target version. No history is ever lost.
Get version history for a page. Returns metadata (no content) sorted newest-first.
Example Response
{
"success": true,
"data": {
"slug": "about",
"current_version": 4,
"versions": [
{
"version": 4,
"hash": "jkl012mn",
"created_by": "patch",
"patch_summary": "Updated page title",
"size_bytes": 2340,
"created_at": "2026-02-13T15:00:00Z"
},
{
"version": 3,
"hash": "a3f8c2d1",
"created_by": "update",
"size_bytes": 2280,
"created_at": "2026-02-13T14:00:00Z"
}
]
}
}
"create" — initial page creation, "update" — full PUT replacement, "patch" — PATCH operation, "rollback" — rollback to earlier version.
Rollback a page to a previous version. Creates a new version with the content of the target version (forward-only — the version number always increments).
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
target_version | integer | One of two | Version number to rollback to |
target_version_hash | string | One of two | Version hash to rollback to (alternative) |
Example
// Request
{ "target_version": 2 }
// Response
{
"success": true,
"data": {
"slug": "about",
"rolled_back_to": 2,
"new_version": 5,
"new_version_hash": "xyz789ab"
}
}
Fragments
Fragments are reusable HTML blocks (navigation, footer, header) that can be included in any page. When a fragment is updated, all pages using it automatically serve the new version — no page re-saves needed.
Include-Tag Syntax
Embed a fragment in any page using an HTML comment tag:
<!--#wps-include fragment="menu" -->
<!--#wps-include fragment="footer" -->
<!--#wps-include fragment="header/main" -->
The tag is valid HTML (a comment), so if injection fails the page renders gracefully with the tag removed. Fragment names support lowercase alphanumeric characters, hyphens, and slashes.
Fragment CRUD
Fragments are stored as pages with the _fragment/ slug prefix and papi_type = 'fragment'. You can use the standard pages endpoints, but dedicated MCP tools are also available.
Create a fragment by using the _fragment/ prefix in the slug.
{
"slug": "_fragment/menu",
"content": "<nav><a href=\"/\">Home</a><a href=\"/about\">About</a></nav>"
}
Update a fragment. Cache is automatically invalidated — all pages using it serve the new content on the next request.
List all fragments in the project.
Delete a fragment. Pages that reference it will render with the tag removed (graceful degradation).
MCP Tools
When working via the MCP server, use the dedicated fragment tools for convenience:
| Tool | Description |
|---|---|
create_fragment | Create a new fragment by name |
update_fragment | Update content (cache auto-invalidated) |
list_fragments | List all fragments in a project |
delete_fragment | Delete a fragment |
Example: Shared Navigation
// 1. Create the fragment
POST /papi/project/12345/pages
{
"slug": "_fragment/menu",
"content": "<nav><a href=\"/\">Home</a><a href=\"/about\">About</a><a href=\"/contact\">Contact</a></nav>"
}
// 2. Use it in a page
POST /papi/project/12345/pages
{
"slug": "about",
"content": "<!DOCTYPE html>\n<html>\n<body>\n <!--#wps-include fragment=\"menu\" -->\n <h1>About</h1>\n</body>\n</html>"
}
// 3. Update the fragment — all pages update instantly
PUT /papi/project/12345/pages/_fragment/menu
{
"content": "<nav><a href=\"/\">Home</a><a href=\"/about\">About</a><a href=\"/blog\">Blog</a><a href=\"/contact\">Contact</a></nav>"
}
Assets
List all assets in the project.
Upload a new asset.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
slug | string | Yes | File path (e.g., "images/logo.png") |
content | string | One of two | Base64 encoded binary data |
source_url | string | One of two | Public URL to fetch the asset from. The server downloads and stores the file with SSRF protection. |
alt | string | No | Alt text for images |
content (base64) or source_url (URL fetch). When using source_url, the server validates content-type, enforces size limits, and protects against SSRF.
Supported File Types
| Category | Extensions | Max Size |
|---|---|---|
| Images | jpg, jpeg, png, gif, webp, svg | 5 MB |
| Documents | 5 MB | |
| Fonts | woff, woff2, ttf | 2 MB |
| Code | js, css | 1 MB |
| Other | ico, json, svg | 1 MB |
Get a specific asset. Returns metadata and base64-encoded content for binary files, or plain text for code assets (JS, CSS, JSON, SVG).
Apply targeted text patches to a code asset (JS, CSS, JSON, SVG) without re-uploading the full file. Same find-and-replace pattern as page patches. For binary assets (images, fonts), use POST with overwrite: true instead.
Patch Operations
| Operation | Description |
|---|---|
replace | Find and replace text |
delete | Find and remove text |
insert_before | Insert content before the found text |
insert_after | Insert content after the found text |
Delete an asset. Removes the file from S3 and CDN.
Tracking Scripts
Inject Google Analytics, Google Tag Manager, Meta Pixel, or any other tracking code into all pages of a project. Scripts are stored per-project and injected at serve time by the Optimizer.
Get the current tracking scripts for a project.
Example Response
{
"success": true,
"data": {
"head_scripts": "<script>...Google Analytics...</script>",
"body_scripts": "<noscript>...Meta Pixel fallback...</noscript>"
}
}
Set or update tracking scripts. head_scripts are injected before </head>, body_scripts before </body>.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
head_scripts | string | No | Scripts injected before </head> (analytics, GTM) |
body_scripts | string | No | Scripts injected before </body> (conversion pixels, noscript fallbacks) |
Remove all tracking scripts from a project.
set_tracking_scripts, get_tracking_scripts, and remove_tracking_scripts for managing tracking via conversation.
Page Metadata
Pages support metadata fields for SEO, language, and redirects. Pass these in the meta object when creating or updating a page.
| Field | Default | Description |
|---|---|---|
seo_title | Page name | Shown in browser tab and search results |
seo_description | — | Search result snippet (150–160 chars ideal) |
seo_keywords | — | Maximum 9, comma-separated |
seo_robots_index | false | Set true to include page in sitemap and search engines |
seo_robots_follow | false | Set true to allow search engines to follow links |
page_language | — | ISO code (e.g., "en", "nl", "de") |
landingpage | false | Set true to make this page the homepage |
redirect_code | — | 301 (permanent) or 302 (temporary) — turns page into a redirect |
redirect_destination | — | Full URL or relative path for redirect target |
seo_robots_index is true. Pages without this flag are live but not discoverable — useful for soft launches or pages still being built.
redirect_code (301 or 302) and redirect_destination. Do not include HTML content.
Optimizer Comment Tags
Every page should include these HTML comment tags for the platform's SEO engine to work correctly. The Optimizer replaces them at serve time with canonical tags, Open Graph meta, and injected scripts.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Page Title</title>
<!-- Optimizer - Canonical -->
<!-- Optimizer - Custom Meta -->
<!-- Optimizer - Open Graph -->
<!-- Optimizer - Header Javascripts -->
</head>
<body>
<!-- page content here -->
<!-- Optimizer - Footer Javascripts -->
</body>
</html>
| Tag | Injected Content |
|---|---|
<!-- Optimizer - Canonical --> | Canonical URL link tag |
<!-- Optimizer - Custom Meta --> | SEO meta description and keywords |
<!-- Optimizer - Open Graph --> | OG title, description, image tags |
<!-- Optimizer - Header Javascripts --> | Tracking scripts (head), custom scripts |
<!-- Optimizer - Footer Javascripts --> | Tracking scripts (body), conversion pixels |
Bulk Operations
For efficiency, use bulk endpoints when creating multiple pages or assets in one request.
Create multiple pages in one request.
{
"pages": [
{ "slug": "index", "content": "...", "meta": {...} },
{ "slug": "about", "content": "...", "meta": {...} }
]
}
Upload multiple assets in one request.
{
"assets": [
{ "slug": "images/a.png", "content": "{base64}" },
{ "slug": "images/b.png", "source_url": "https://example.com/b.png" }
]
}
Error Handling
All errors return a consistent JSON format:
{
"success": false,
"error": {
"message": "Description of the error",
"code": 400
}
}
HTTP Status Codes
| Code | Meaning |
|---|---|
200 | Success |
201 | Created |
400 | Bad Request — invalid input |
401 | Unauthorized — invalid token or project mismatch |
404 | Not Found — resource does not exist |
409 | Conflict — resource already exists or version conflict (PATCH) |
422 | Validation Error |
429 | Rate Limited — too many requests |
500 | Server Error |
Complete Example
Build a simple website with a shared navigation fragment, two pages, tracking, and SEO metadata:
# 1. Check project status
curl -X GET "https://api.websitepublisher.ai/papi/project/{id}/status" \
-H "Authorization: Bearer YOUR_TOKEN"
# 2. Create a shared navigation fragment
curl -X POST "https://api.websitepublisher.ai/papi/project/{id}/pages" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slug": "_fragment/nav",
"content": "<nav><a href=\"/\">Home</a><a href=\"/about\">About</a></nav>"
}'
# 3. Create pages using the fragment
curl -X POST "https://api.websitepublisher.ai/papi/project/{id}/pages/bulk" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pages": [
{
"slug": "index",
"content": "<!DOCTYPE html>...<!--#wps-include fragment=\"nav\" -->...</html>",
"meta": {"title": "Home", "landingpage": true},
"seo": {"description": "Welcome to our site", "robots_index": true, "robots_follow": true}
},
{
"slug": "about",
"content": "<!DOCTYPE html>...<!--#wps-include fragment=\"nav\" -->...</html>",
"meta": {"title": "About"},
"seo": {"description": "Learn about us", "robots_index": true}
}
]
}'
# 4. Add Google Analytics tracking
curl -X PUT "https://api.websitepublisher.ai/papi/project/{id}/tracking" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"head_scripts": "<script async src=\"https://www.googletagmanager.com/gtag/js?id=G-XXXXXX\"></script>"}'
# 5. Update a page with PATCH
curl -X PATCH "https://api.websitepublisher.ai/papi/project/{id}/pages/index" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"base_version_hash": "abc123",
"patches": [{"operation": "replace", "find": "<h1>Welcome</h1>", "replace": "<h1>Hello World</h1>"}],
"patch_summary": "Updated heading"
}'
Powered by WebSumo