Dashboard
How it Works Documentation Quick Start PAPI — Pages & Assets MAPI — Dynamic Data Integrations SAPI — Sessions & Forms MCP Server OpenClaw Skill Tools Deploy Dashboard

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 PrefixScopeDescription
wps_...ProjectScoped to a single project
wpa_...AccountAccess all projects owned by the account
OAuthUserIssued via OAuth 2.1 + PKCE (MCP, GPT Actions)
⚠️ Important For project-scoped tokens (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:

APIBase PathPurpose
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 /papi/project/{project_id}/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

GET /papi/project/{project_id}/pages

List all pages in the project. Add ?type=page to exclude fragments, or ?type=fragment to list only fragments.

POST /papi/project/{project_id}/pages

Create a new page.

Request Body

FieldTypeRequiredDescription
slugstringYesURL path (e.g., "about", "blog/post1")
contentstringYesFull HTML content starting with <!DOCTYPE html>
metaobjectNoPage metadata (title, language, redirects — see Page Metadata)
seoobjectNoSEO 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 /papi/project/{project_id}/pages/{slug}

Get a specific page by slug. Returns the source HTML (with include-tags intact for pages using fragments).

Query Parameters

ParameterTypeDefaultDescription
include_versionbooleanfalseInclude 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"
    }
}
PUT /papi/project/{project_id}/pages/{slug}

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 /papi/project/{project_id}/pages/{slug}

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.

PATCH /papi/project/{project_id}/pages/{slug}

Apply one or more patch operations to a page.

Request Body

FieldTypeRequiredDescription
base_version_hashstringYesHash from GET ?include_version=true. Prevents overwrites if page changed.
patchesarrayYesArray of patch operations (see below)
patch_summarystringNoHuman-readable description of the change (stored in version history)

Patch Operations

FieldTypeDescription
operationstring"replace" or "delete"
findstringExact text to search for (must exist exactly once in current content)
replacestringText 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 (409) If the page was modified since your last fetch, a 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 /papi/project/{project_id}/pages/{slug}/versions

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"
            }
        ]
    }
}
ℹ️ created_by values "create" — initial page creation, "update" — full PUT replacement, "patch" — PATCH operation, "rollback" — rollback to earlier version.
POST /papi/project/{project_id}/pages/{slug}/rollback

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

FieldTypeRequiredDescription
target_versionintegerOne of twoVersion number to rollback to
target_version_hashstringOne of twoVersion 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.

ℹ️ How it works Pages are stored with include-tags intact. At serve time, the Optimizer resolves each tag by fetching the fragment from Redis cache (or S3 on cache miss, with 10-minute TTL). When a fragment is updated, its cache is invalidated — the next page request picks up the new version.

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.

POST /papi/project/{project_id}/pages

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>"
}
PUT /papi/project/{project_id}/pages/_fragment/{name}

Update a fragment. Cache is automatically invalidated — all pages using it serve the new content on the next request.

GET /papi/project/{project_id}/pages?type=fragment

List all fragments in the project.

DELETE /papi/project/{project_id}/pages/_fragment/{name}

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:

ToolDescription
create_fragmentCreate a new fragment by name
update_fragmentUpdate content (cache auto-invalidated)
list_fragmentsList all fragments in a project
delete_fragmentDelete 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

GET /papi/project/{project_id}/assets

List all assets in the project.

POST /papi/project/{project_id}/assets

Upload a new asset.

Request Body

FieldTypeRequiredDescription
slugstringYesFile path (e.g., "images/logo.png")
contentstringOne of twoBase64 encoded binary data
source_urlstringOne of twoPublic URL to fetch the asset from. The server downloads and stores the file with SSRF protection.
altstringNoAlt text for images
ℹ️ Two upload methods Provide either 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

CategoryExtensionsMax Size
Imagesjpg, jpeg, png, gif, webp, svg5 MB
Documentspdf5 MB
Fontswoff, woff2, ttf2 MB
Codejs, css1 MB
Otherico, json, svg1 MB
GET /papi/project/{project_id}/assets/{slug}

Get a specific asset. Returns metadata and base64-encoded content for binary files, or plain text for code assets (JS, CSS, JSON, SVG).

PATCH /papi/project/{project_id}/assets/{slug}

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

OperationDescription
replaceFind and replace text
deleteFind and remove text
insert_beforeInsert content before the found text
insert_afterInsert content after the found text
DELETE /papi/project/{project_id}/assets/{slug}

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 /papi/project/{project_id}/tracking

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>"
    }
}
PUT /papi/project/{project_id}/tracking

Set or update tracking scripts. head_scripts are injected before </head>, body_scripts before </body>.

Request Body

FieldTypeRequiredDescription
head_scriptsstringNoScripts injected before </head> (analytics, GTM)
body_scriptsstringNoScripts injected before </body> (conversion pixels, noscript fallbacks)
DELETE /papi/project/{project_id}/tracking

Remove all tracking scripts from a project.

ℹ️ MCP Tools The MCP server provides 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.

FieldDefaultDescription
seo_titlePage nameShown in browser tab and search results
seo_descriptionSearch result snippet (150–160 chars ideal)
seo_keywordsMaximum 9, comma-separated
seo_robots_indexfalseSet true to include page in sitemap and search engines
seo_robots_followfalseSet true to allow search engines to follow links
page_languageISO code (e.g., "en", "nl", "de")
landingpagefalseSet true to make this page the homepage
redirect_code301 (permanent) or 302 (temporary) — turns page into a redirect
redirect_destinationFull URL or relative path for redirect target
ℹ️ SEO tip Pages only appear in the sitemap when seo_robots_index is true. Pages without this flag are live but not discoverable — useful for soft launches or pages still being built.
ℹ️ Redirects To create a redirect, create a page with the source slug, set 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>
TagInjected 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
⚠️ Important Without these tags, the Optimizer cannot inject canonical URLs, Open Graph headers, or tracking scripts. Always include them in every page — they are invisible to visitors.

Bulk Operations

For efficiency, use bulk endpoints when creating multiple pages or assets in one request.

POST /papi/project/{project_id}/pages/bulk

Create multiple pages in one request.

{
    "pages": [
        { "slug": "index", "content": "...", "meta": {...} },
        { "slug": "about", "content": "...", "meta": {...} }
    ]
}
POST /papi/project/{project_id}/assets/bulk

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

CodeMeaning
200Success
201Created
400Bad Request — invalid input
401Unauthorized — invalid token or project mismatch
404Not Found — resource does not exist
409Conflict — resource already exists or version conflict (PATCH)
422Validation Error
429Rate Limited — too many requests
500Server 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