# htmlpub — Complete Reference > Publish HTML pages, blogs, and ecommerce sites instantly. Drop an HTML file or paste code, get a shareable URL in seconds. ## Overview htmlpub is the fastest way to publish HTML on the web. Users can paste HTML at htmlpub.com (free, no account needed) or use the REST API to publish programmatically (Pro plan). The platform also supports blogging with markdown, analytics, form collection, ecommerce, team organizations, and AI-powered content generation. **Base URL**: `https://htmlpub.com` **Auth format**: `Authorization: Bearer hp_live_YOUR_API_KEY` **Content type**: `application/json` (except asset uploads which use `multipart/form-data`) ## Plans | Feature | Free | Starter ($10/mo) | Pro ($25/mo) | |---------|------|-------------------|--------------| | Max pages | 5 | 50 | 250 | | Max blogs | 0 | 1 | 3 | | Page expiry | 7 days | Never | Never | | Custom slugs | No | Yes | Yes | | API access | No | No | Yes | | Custom domains | 0 | 1 | 5 | | Analytics | Basic (7 days) | Full (30 days) | Pro (90 days) + heatmaps | | Max asset size | 2MB | 5MB | 10MB | | Total storage | 50MB | 500MB | 5GB | | Assets per page | 15 | 50 | Unlimited | | Max integrations | 0 | 2 | 5 | | Team seats | 1 | 1 | 3 ($5/mo per extra) | | AI credits/mo | 5,000 | 20,000 | 60,000 | | Conversion goals | 1 | 3 | Unlimited | ### AI Credit Top-Up Packs | Credits | Price | Per 1K credits | |---------|-------|----------------| | 2,000 | $2.50 | $1.25 | | 5,000 | $5.00 | $1.00 | | 15,000 | $12.00 | $0.80 | | 50,000 | $35.00 | $0.70 | --- ## Authentication All API requests (except public endpoints like `/raw`, form submission, and analytics collection) require a Bearer token: ``` Authorization: Bearer hp_live_YOUR_API_KEY ``` Get API keys at https://htmlpub.com/dashboard/api-keys (Pro plan required, max 5 keys). --- ## Pages API ### PUT /api/pages/{slug} Create or update a page with a custom slug. **API key auth only** (not session). **Slug rules**: 3-100 characters, lowercase alphanumeric and hyphens, pattern: `^[a-z0-9]+(-[a-z0-9]+)*$` **Request body**: ```json { "html": "...", "siteId": "optional-site-id" } ``` - `html` (required): Complete HTML content, max 5MB - `siteId` (optional): Assign page to a site you own. Set to `null` to unassign. **Response** (201 Created / 200 OK): ```json { "id": "cm1a2b3c4d5e6f7g8h9i0", "slug": "my-page", "url": "https://htmlpub.com/p/my-page", "siteId": null } ``` **Errors**: 400 (invalid slug/HTML), 401 (invalid API key), 403 (plan limit), 409 (slug taken) ### PATCH /api/pages/{slug} Update an existing page. Supports session or API key auth. **Request body** (all fields optional, send at least one): ```json { "html": "updated...", "title": "New Title", "siteId": "site-id-or-null" } ``` Also accepts `multipart/form-data` with a `file` field for HTML upload. **Response** (200 OK): ```json { "slug": "my-page", "url": "https://htmlpub.com/p/my-page", "title": "New Title", "siteId": null } ``` ### GET /api/pages List all pages for the authenticated user. **Response** (200 OK): ```json { "pages": [ { "id": "cm1a2b3c4d5e6f7g8h9i0", "slug": "my-page", "title": "My Page", "url": "https://htmlpub.com/p/my-page", "siteId": null, "createdAt": "2025-01-15T10:30:00.000Z" } ] } ``` ### DELETE /api/pages/{slug} Permanently delete a page you own. **Response** (200 OK): `{ "success": true }` ### GET /api/pages/{slug}/raw Get raw HTML content of a published page. **No authentication required** for public pages. **Response**: Raw HTML with `Content-Type: text/html` Returns 404 if page doesn't exist or has expired. --- ## Assets API Upload images, CSS, JavaScript, and fonts to your pages. **Accepted file types**: png, jpg, gif, svg, webp, css, js, woff, woff2, ttf ### POST /api/pages/{slug}/assets Upload a file to a page. **Request**: `multipart/form-data` with `file` field **Response** (201 Created): ```json { "asset": { "id": "asset_abc123", "filename": "logo.png", "contentType": "image/png", "size": 45678 }, "url": "https://htmlpub.com/api/pages/my-page/assets/asset_abc123" } ``` **Errors**: 400 (invalid file type/too large), 403 (storage/asset count limit) ### GET /api/pages/{slug}/assets List all assets for a page. **Response** (200 OK): ```json { "assets": [ { "id": "asset_abc123", "filename": "logo.png", "contentType": "image/png", "size": 45678, "createdAt": "2025-01-15T10:30:00.000Z" } ] } ``` ### GET /api/pages/{slug}/assets/{assetId} Serve an asset file. **No authentication required** for public pages. Returns the file with appropriate Content-Type. Cache: `public, max-age=31536000, immutable` ### DELETE /api/pages/{slug}/assets/{assetId} Delete a specific asset. **Response**: `{ "success": true }` ### DELETE /api/pages/{slug}/assets Delete all assets for a page. **Response**: `{ "success": true }` --- ## Sites API Group related pages into sites with their own URL namespace. ### POST /api/sites Create a new site. **Request body**: ```json { "slug": "my-blog", "name": "My Blog" } ``` **Response** (201 Created): ```json { "id": "site_abc123", "slug": "my-blog", "name": "My Blog", "url": "https://htmlpub.com/s/my-blog", "createdAt": "2025-01-15T10:30:00.000Z" } ``` ### GET /api/sites List all sites with their pages. **Response** (200 OK): ```json { "sites": [ { "id": "site_abc123", "slug": "my-blog", "name": "My Blog", "url": "https://htmlpub.com/s/my-blog", "defaultPageId": null, "pageCount": 3, "pages": [...], "createdAt": "2025-01-15T10:30:00.000Z" } ] } ``` ### GET /api/sites/{siteId} Get site details including all pages. ### PATCH /api/sites/{siteId} Update a site's name, slug, or default page. **Request body** (all optional): ```json { "name": "Updated Name", "slug": "new-slug", "defaultPageId": "page-id-or-null" } ``` ### DELETE /api/sites/{siteId} Delete a site. **Warning**: This cascading-deletes all pages in the site. **Response**: `{ "success": true, "message": "Site deleted" }` --- ## Blogs API Create and manage blogs with markdown posts, custom templates, and landing pages. **Requires Starter or Pro plan.** ### POST /api/blogs Create a new blog. **Request body**: ```json { "slug": "my-tech-blog", "title": "My Tech Blog", "description": "Optional description", "authorName": "Default author" } ``` **Response** (201 Created): ```json { "id": "blog_abc123", "slug": "my-tech-blog", "title": "My Tech Blog", "description": "Optional description", "authorName": "Default author", "createdAt": "2025-01-15T10:30:00.000Z" } ``` **Errors**: 400 (invalid slug), 403 (blog limit/feature disabled), 409 (slug exists), 422 (blocked slug) ### GET /api/blogs List all blogs with post counts. **Response** (200 OK): ```json { "blogs": [ { "id": "blog_abc123", "slug": "my-tech-blog", "title": "My Tech Blog", "description": "Optional description", "authorName": "Default author", "postCount": 12, "createdAt": "2025-01-15T10:30:00.000Z" } ] } ``` ### GET /api/blogs/{blogId} Get a single blog's details. ### PATCH /api/blogs/{blogId} Update blog settings. **Request body** (all optional): `{ title, description, authorName, slug, siteId }` ### DELETE /api/blogs/{blogId} Delete a blog. **Warning**: Cascading-deletes all posts and templates. ### Blog Posts #### POST /api/blogs/{blogId}/posts Create a new blog post. **Request body**: ```json { "title": "My First Post", "markdown": "# Hello World\n\nThis is my first blog post.", "slug": "my-first-post", "excerpt": "A short description", "tags": ["tech", "tutorial"], "authorName": "Author Name", "status": "DRAFT", "publishedAt": "2025-02-01T00:00:00.000Z", "templateId": "template_abc123", "coverImagePath": "/api/blogs/blog123/assets/asset456" } ``` - `title` (required): Post title - `markdown` (required): Post content in markdown - `slug` (optional): URL slug, auto-generated from title if omitted - `status`: `"DRAFT"` (default), `"PUBLISHED"`, or `"SCHEDULED"` - `publishedAt` (optional): Schedule date. If status is PUBLISHED and date is future, auto-converts to SCHEDULED - All other fields are optional **Response** (201 Created): ```json { "id": "post_abc123", "slug": "my-first-post", "title": "My First Post", "status": "DRAFT", "url": null, "publishedAt": null } ``` #### GET /api/blogs/{blogId}/posts List posts in a blog. **Query params**: `status` (DRAFT|PUBLISHED|SCHEDULED, optional) **Response**: `{ "posts": [...] }` #### GET /api/blogs/{blogId}/posts/{postId} Get a post with full markdown content. #### PATCH /api/blogs/{blogId}/posts/{postId} Update a post. All fields optional: `{ title, markdown, slug, excerpt, tags, authorName, status, publishedAt, coverImagePath, templateId }` **Auto-status logic**: - Setting status to PUBLISHED with a future `publishedAt` → auto-converts to SCHEDULED - Setting status to SCHEDULED with a past `publishedAt` → auto-converts to PUBLISHED #### DELETE /api/blogs/{blogId}/posts/{postId} Delete a post. **Response**: `{ "success": true }` #### POST /api/blogs/{blogId}/posts/generate AI-generate a blog post. Returns Server-Sent Events stream. **Request body**: `{ "prompt": "Write about...", "type": "post" | "landing" | "template" }` **SSE events**: ``` data: { "type": "text_delta", "text": "..." } data: { "type": "done", "inputTokens": 500, "outputTokens": 1200, "creditsUsed": 85 } ``` **Errors**: 402 (insufficient credits), 429 (rate limit) #### POST /api/blogs/{blogId}/posts/preview Preview a post rendered through its template. Returns HTML. **Request body**: `{ title, markdown, excerpt, tags, authorName, templateId }` ### Blog Templates HTML templates define how blog posts are rendered. Templates must include markers: ``, ``, ``, ``, `` #### POST /api/blogs/{blogId}/templates Create a template. **Request body**: `{ "name": "Template Name", "html": "...", "isDefault": true }` #### GET /api/blogs/{blogId}/templates List templates for a blog. #### GET /api/blogs/{blogId}/templates/{templateId} Get template with HTML content. #### PUT /api/blogs/{blogId}/templates/{templateId} Update template HTML or name. #### PATCH /api/blogs/{blogId}/templates/{templateId} Set template as default. #### DELETE /api/blogs/{blogId}/templates/{templateId} Delete template. Posts using it fall back to default. ### Blog Landing Pages Each blog can have a custom HTML landing page. Use `` marker where post listings should be injected. #### GET /api/blogs/{blogId}/landing Get the landing page HTML. #### POST /api/blogs/{blogId}/landing Create/update the landing page. Accepts FormData with `file` field or JSON with `html`. #### DELETE /api/blogs/{blogId}/landing Remove the landing page. ### Blog Assets #### POST /api/blogs/{blogId}/assets Upload an image to a blog. Multipart form upload, `file` field. Supports PNG, JPEG, GIF, WebP, SVG (max 10MB). **Response**: `{ assetId, filename, url, contentType, fileSize }` #### GET /api/blogs/{blogId}/assets/{assetId} Serve a blog asset. No auth required. Cache: 1 year (immutable). ### Public Blog Rendering These endpoints serve rendered blog pages to the public (no auth required): - `GET /api/blogs/render/{blogSlug}` — Blog landing page with post listings. Query: `tag` for filtering. - `GET /api/blogs/render/{blogSlug}/{postSlug}` — Rendered blog post - `GET /api/blogs/render/s/{siteSlug}/{postSlug}` — Site blog post --- ## Forms API htmlpub auto-detects HTML forms on published pages and collects submissions. ### POST /api/forms/submit Submit form data. **Public endpoint, CORS enabled.** **Rate limit**: 10 submissions/minute per IP **Request body**: ```json { "pageSlug": "my-page", "formIdentifier": "contact-form", "data": { "name": "Jane", "email": "jane@example.com", "message": "Hello" } } ``` - `data`: Max 50KB, max 100 fields, HTML tags stripped, max 10KB per field **Response** (201): `{ "id": "...", "message": "Form submission received" }` **Errors**: 400 (validation), 403 (collection disabled), 404 (page not found), 429 (rate limit) ### GET /api/forms List pages with form submissions. **Response**: ```json { "pages": [ { "id": "...", "slug": "my-page", "title": "My Page", "formCollectionEnabled": true, "hasForm": true, "submissionCount": 42, "lastSubmissionAt": "2025-01-15T10:30:00.000Z" } ] } ``` ### GET /api/forms/{pageSlug} Get submissions for a page. **Query params**: `limit` (1-100, default 50), `offset` (default 0) **Response**: ```json { "page": { "id": "...", "slug": "my-page", "title": "My Page", "formCollectionEnabled": true }, "submissions": [ { "id": "...", "formIdentifier": "contact-form", "data": { "name": "Jane", "email": "jane@example.com" }, "submitterIp": "1.2.3.4", "submitterUserAgent": "...", "createdAt": "..." } ], "pagination": { "total": 42, "limit": 50, "offset": 0, "hasMore": false } } ``` ### DELETE /api/forms/{pageSlug}/{submissionId} Delete a form submission. --- ## Analytics API Track page views, scroll depth, clicks, and conversions. ### POST /api/analytics/collect Track analytics events. **Public endpoint, CORS enabled.** **Rate limit**: 30 requests/minute per IP **Request body**: ```json { "events": [ { "pageId": "...", "eventType": "page_view", "visitorId": "...", "sessionId": "...", "timestamp": 1705312200000 } ] } ``` Event types: `page_view`, `scroll_depth`, `element_click`, `form_submit`, `rum_timing` **Max**: 50 events per batch **Response** (202): `{ "accepted": 5 }` ### GET /api/analytics/overview Dashboard overview with trends. **Starter+ plan required.** **Query params**: `range` (7d|14d|30d|90d, default 7d) **Response**: ```json { "totalViews": 1234, "totalUniqueVisitors": 567, "totalSessions": 890, "avgScrollDepth": 0.65, "totalFormSubmissions": 23, "totalConversions": 12, "dailyMetrics": [{ "date": "2025-01-15", "views": 100, "uniqueVisitors": 50, "sessions": 75 }], "topSources": [{ "source": "google", "count": 200 }], "deviceBreakdown": [{ "device": "desktop", "count": 800 }], "comparison": { "viewsChange": 15.2, "visitorsChange": 8.5 } } ``` ### GET /api/analytics/pages Per-page metrics summary. **Query params**: `range` (default 7d) **Response**: ```json { "pages": [ { "pageId": "...", "slug": "my-page", "title": "My Page", "views": 500, "uniqueVisitors": 200, "avgScrollDepth": 0.72, "conversions": 5, "conversionRate": 0.025 } ] } ``` ### GET /api/analytics/pages/{pageId} Detailed analytics for a single page. **Query params**: `range` (default 7d) **Response**: ```json { "pageId": "...", "dailyMetrics": [...], "scrollFunnel": [{ "depth": 25, "reached": 450 }, { "depth": 50, "reached": 300 }], "topSources": [...], "deviceBreakdown": [...], "browserBreakdown": [...], "topClickedElements": [{ "selector": ".cta-button", "count": 45 }], "totals": { "views": 500, "uniqueVisitors": 200, "sessions": 350, "avgScrollDepth": 0.72, "formSubmissions": 10, "conversions": 5, "medianLoadTimeMs": 1200 } } ``` ### GET /api/analytics/pages/{pageId}/heatmap Click heatmap data. **Pro plan only.** **Query params**: `range`, `viewport` (desktop|tablet|mobile, default desktop) **Response**: ```json { "clicks": [{ "xBin": 45, "yBin": 120, "count": 12 }], "scrollDepth": [{ "depth": 25, "reached": 450 }] } ``` Heatmap uses a 100x1000 pixel grid. `xBin` (0-99) = horizontal position, `yBin` (0-999) = vertical position. --- ## Organizations API Team workspaces with role-based access control. **Roles**: OWNER (full control), ADMIN (manage members, resources), MEMBER (use resources) ### POST /api/orgs Create an organization. **Request body**: `{ "name": "My Team" }` ### GET /api/orgs List organizations you belong to. ### GET /api/orgs/{orgId} Get organization details. ### PUT /api/orgs/{orgId} Update organization name. ### DELETE /api/orgs/{orgId} Delete organization (OWNER only). Must be empty. ### POST /api/orgs/{orgId}/avatar Upload organization avatar. Multipart form upload, `file` field. ### POST /api/orgs/{orgId}/invites Invite a team member. **Request body**: `{ "email": "user@example.com", "role": "MEMBER" }` ### GET /api/orgs/{orgId}/invites List pending invitations. ### PATCH /api/orgs/{orgId}/invites/{inviteId} Update invitation (e.g., revoke). ### DELETE /api/orgs/{orgId}/invites/{inviteId} Delete invitation. ### GET /api/orgs/{orgId}/members List organization members. ### PATCH /api/orgs/{orgId}/members/{userId} Update member role. ### DELETE /api/orgs/{orgId}/members/{userId} Remove member from organization. ### GET /api/orgs/invites/{token} Get invitation details by token. ### POST /api/orgs/invites/accept Accept an invitation. **Request body**: `{ "token": "invite_token_here" }` ### POST /api/orgs/switch Switch active organization context. **Request body**: `{ "organizationId": "org_abc123" }` --- ## Tags API Color-coded tags for organizing pages, sites, and forms. ### POST /api/tags Create a tag. **Request body**: `{ "name": "Important", "color": "#FF5733" }` - `name`: 1-32 characters - `color`: Optional hex color **Response** (201): Tag object **Errors**: 409 (name exists) ### GET /api/tags List all tags. ### PATCH /api/tags/{tagId} Update tag name or color. ### DELETE /api/tags/{tagId} Delete tag. Cascades to remove all assignments. ### GET /api/tags/{tagId}/usage Get usage counts and entities for a tag. **Response**: ```json { "counts": { "page": 5, "site": 2, "form": 1 }, "entities": [{ "type": "page", "id": "...", "name": "My Page" }] } ``` ### POST /api/tags/{tagId}/merge Merge a tag into another. Moves all assignments, deletes source. **Request body**: `{ "targetId": "tag_target123" }` ### POST /api/tags/assignments Assign a tag to an entity. **Request body**: `{ "tagId": "tag_abc", "entityType": "page", "entityId": "page_123" }` Entity types: `page`, `site`, `form` ### GET /api/tags/assignments List all tag assignments. ### DELETE /api/tags/assignments Remove a tag assignment. **Request body**: `{ "tagId": "...", "entityType": "page", "entityId": "..." }` ### POST /api/tags/assignments/bulk Bulk assign or remove tags. **Request body**: ```json { "tagId": "tag_abc", "entityType": "page", "entityIds": ["page_1", "page_2", "page_3"], "action": "assign" } ``` Max 500 entities per request. --- ## Custom Domains API Connect your own domain to a page, site, or blog. **Pro plan required.** ### POST /api/custom-domains Add a custom domain. **Request body**: ```json { "domain": "www.example.com", "pageId": "cm1a2b3c4d5e6f7g8h9i0" } ``` **Response** (201): ```json { "domain": { "domain": "www.example.com", "status": "PENDING", "pageId": "cm1a2b3c4d5e6f7g8h9i0", "verifiedAt": null, "createdAt": "2025-01-15T10:30:00.000Z" }, "instructions": { "target": "customers.htmlpub.com", "message": "Add a CNAME record pointing www.example.com to customers.htmlpub.com" } } ``` Domain statuses: PENDING → CONFIGURING → DNS_PROPAGATING → SSL_PENDING → ACTIVE ### GET /api/custom-domains List all custom domains. ### GET /api/custom-domains/{domain} Get domain details. ### PATCH /api/custom-domains/{domain} Change which page/site/blog a domain points to. **Request body**: `{ "pageId": "new-page-id" }` ### DELETE /api/custom-domains/{domain} Remove a custom domain. ### POST /api/custom-domains/{domain}/verify Verify DNS is properly configured. **Response**: `{ "domain": { ... }, "verified": true, "cfStatus": "active" }` --- ## Credits API AI credits for page generation and blog post writing. ### GET /api/credits/balance Check current credit balance. **Response**: `{ "balance": 15000 }` ### GET /api/credits/history Get credit transaction history. **Query params**: `limit` (max 100, default 50), `offset` (default 0) **Response**: ```json { "entries": [ { "id": "...", "amount": -85, "type": "AI_DEBIT", "description": "Blog post generation", "balanceAfter": 14915, "createdAt": "2025-01-15T10:30:00.000Z" } ] } ``` Credit types: `MONTHLY_GRANT`, `WEEKLY_GRANT`, `INITIAL_GRANT`, `TOPUP`, `AI_DEBIT`, `RESERVATION`, `RELEASE`, `EXPIRY`, `ADMIN_ADJUST` --- ## Integrations API Connect to third-party services (Slack, Google Sheets, Notion, etc.) via Paragon. ### GET /api/integrations/providers List available integration providers and their actions. **Response**: ```json { "providers": [ { "id": "slack", "displayName": "Slack", "icon": "...", "connected": true, "actions": [ { "name": "send_message", "displayName": "Send Message", "triggers": ["form_submitted"] } ] } ] } ``` ### GET /api/integrations/status Check connection status for all integrations. ### POST /api/integrations/bindings Create an integration binding (connect a trigger to an action). **Request body**: ```json { "featureTrigger": "form_submitted", "providerId": "slack", "actionName": "send_message", "enabled": true, "config": { "parameterMappings": { "channel": "#leads" }, "staticParameters": { "message_prefix": "New lead:" } }, "pageSlug": "my-landing-page" } ``` ### GET /api/integrations/bindings List all integration bindings. ### DELETE /api/integrations/bindings?id={bindingId} Delete an integration binding. ### POST /api/integrations/actions/execute Execute an integration action manually. **Request body**: `{ "action": "send_message", "parameters": { "channel": "#general", "message": "Hello" } }` ### GET /api/integrations/slack/channels List available Slack channels (when Slack is connected). 5-minute cache. ### GET /api/integrations/form-fields Get form field names for a page (for mapping form data to integrations). **Query params**: `pageSlug` (optional) ### GET /api/integrations/logs View integration action execution logs. **Query params**: `page` (default 1), `status` (optional filter) --- ## E-commerce API Accept payments on published pages via Stripe Connect, PayPal, Shopify, or Lemon Squeezy. ### Stripe Connect - `POST /api/connect/v2/create-account` — Create Stripe Connect account - `POST /api/connect/v2/account-link` — Get Stripe onboarding URL - `GET /api/connect/v2/account-status` — Check account status - `POST /api/connect/v2/checkout` — Create checkout session - `GET /api/connect/v2/products` — List Stripe products ### PayPal - `POST /api/connect/paypal/authorize` — Start PayPal connection - `GET /api/connect/paypal/callback` — OAuth callback - `GET /api/connect/paypal/status` — Check connection status - `POST /api/connect/paypal/disconnect` — Disconnect PayPal ### Shopify - `POST /api/connect/shopify/connect` — Connect Shopify store - `GET /api/connect/shopify/status` — Check connection status - `POST /api/connect/shopify/test-connection` — Test connection - `POST /api/connect/shopify/disconnect` — Disconnect Shopify - `POST /api/shopify/cart/checkout-link` — Generate checkout link ### Lemon Squeezy - `POST /api/connect/lemonsqueezy/connect` — Connect Lemon Squeezy - `GET /api/connect/lemonsqueezy/status` — Check connection status - `POST /api/connect/lemonsqueezy/disconnect` — Disconnect - `POST /api/lemonsqueezy/checkout` — Create checkout session ### E-commerce Dashboard - `GET /api/internal/ecommerce/status` — Get all connected gateways - `GET /api/internal/ecommerce/sessions` — List checkout sessions - `GET /api/internal/ecommerce/revenue` — Revenue analytics - `POST /api/internal/ecommerce/add-checkout` — Add checkout to page - `POST /api/internal/ecommerce/remove-checkout` — Remove checkout from page --- ## Error Responses All errors return JSON: ```json { "error": "Error message description" } ``` | Code | Meaning | |------|---------| | 400 | Invalid request body, missing fields, validation error | | 401 | Missing or invalid API key | | 402 | Insufficient AI credits | | 403 | Plan limit reached, feature requires upgrade, or not your resource | | 404 | Resource not found or expired | | 409 | Slug or domain already taken | | 413 | HTML exceeds 5MB limit | | 422 | Reserved slug, action failed | | 429 | Rate limit exceeded (check Retry-After header) | | 500 | Internal server error | ## Rate Limits - Pages API (except /raw): 20 requests per 60 seconds per IP - /raw endpoint: 60 requests per 60 seconds per IP - API keys endpoint: 10 requests per 60 seconds per IP - Form submission: 10 submissions per minute per IP - Analytics collection: 30 requests per minute per IP Rate limit headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining` --- ## MCP Tools Reference htmlpub has a remote MCP server at `https://mcp.htmlpub.com/mcp` with 37 tools that AI assistants (Claude, etc.) can connect to via the Model Context Protocol. OAuth-based auth, no API keys needed. ### Organization Tools **list_organizations** — List all organizations you have access to. Use this to discover which org to target when creating resources. - No parameters - Returns: Array of `{ id, name, isPersonal, isDefault }` ### Page Tools **list_pages** — List your published HTML pages across all organizations. - No parameters - Returns: Array of `{ slug, title, url, status, organizationId, organizationName, createdAt, updatedAt }` **create_page** — Publish an HTML page. Auto-generates a slug by default, or provide a custom slug (Starter/Pro only) to choose the URL or update an existing page. - `html` (string, required): The HTML content to publish - `slug` (string?): Custom URL slug, 3-100 chars (Starter/Pro only). Omit for auto-generated slug. - `title` (string?): Page title (extracted from `