Build and publish pages programmatically
Sign up to get your API key and start creating pages from your code.
API Documentation
Complete reference for the htmlpub REST API
Quick Start
Get started with the htmlpub API in seconds
1. Get Your API Key
Sign in and upgrade to Pro to access API keys in your dashboard.
2. Make Your First Request
curl -X PUT https://htmlpub.com/api/pages/my-page \
-H "Authorization: Bearer hp_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Hello World</h1>"}'Authentication
All API requests require authentication using a Bearer token in the Authorization header.
Format
Authorization: Bearer hp_live_YOUR_API_KEYKeep Your API Key Safe
Never expose your API key in client-side code or public repositories. Use environment variables and keep keys server-side only.
Endpoints
PUT/api/pages/{slug}
Create or update a page with a custom slug
Request Body
{
"html": "<h1>Your HTML content</h1>"
}Success Response (201 Created / 200 OK)
{
"id": "cm1a2b3c4d5e6f7g8h9i0",
"slug": "my-page",
"url": "https://htmlpub.com/my-page"
}Slug Requirements
- 3-100 characters long
- Lowercase letters, numbers, and hyphens only
- Must start and end with alphanumeric character
Examples
cURL
curl -X PUT https://htmlpub.com/api/pages/my-page \
-H "Authorization: Bearer hp_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Hello World</h1><p>My content</p>"}'JavaScript (fetch)
const response = await fetch('https://htmlpub.com/api/pages/my-page', {
method: 'PUT',
headers: {
'Authorization': 'Bearer hp_live_YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
html: '<h1>Hello World</h1><p>My content</p>'
})
});
const data = await response.json();
console.log(data.url); // https://htmlpub.com/my-pagePython (requests)
import requests
response = requests.put(
'https://htmlpub.com/api/pages/my-page',
headers={
'Authorization': 'Bearer hp_live_YOUR_API_KEY',
'Content-Type': 'application/json'
},
json={'html': '<h1>Hello World</h1><p>My content</p>'}
)
data = response.json()
print(data['url']) # https://htmlpub.com/my-pagePATCH/api/pages/{slug}
Update an existing page's content or title
Request Body
{
"html": "<h1>Updated content</h1>"
}
// OR
{
"title": "New Page Title"
}Success Response (200 OK)
{
"id": "cm1a2b3c4d5e6f7g8h9i0",
"slug": "my-page",
"title": "New Page Title",
"url": "https://htmlpub.com/my-page"
}Example
curl -X PATCH https://htmlpub.com/api/pages/my-page \
-H "Authorization: Bearer hp_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Updated content</h1>"}'GET/api/pages
List all pages for your account
Success Response (200 OK)
{
"pages": [
{
"id": "cm1a2b3c4d5e6f7g8h9i0",
"slug": "my-page",
"title": "My Page",
"url": "https://htmlpub.com/my-page",
"siteId": null,
"createdAt": "2024-01-15T10:30:00.000Z"
},
{
"id": "cm2b3c4d5e6f7g8h9i0j1",
"slug": "another-page",
"title": "Another Page",
"url": "https://htmlpub.com/another-page",
"siteId": "site_abc123",
"createdAt": "2024-01-14T09:20:00.000Z"
}
]
}Examples
cURL
curl https://htmlpub.com/api/pages \
-H "Authorization: Bearer hp_live_YOUR_API_KEY"JavaScript (fetch)
const response = await fetch('https://htmlpub.com/api/pages', {
headers: {
'Authorization': 'Bearer hp_live_YOUR_API_KEY'
}
});
const data = await response.json();
console.log(`You have ${data.pages.length} pages`);Python (requests)
import requests
response = requests.get(
'https://htmlpub.com/api/pages',
headers={'Authorization': 'Bearer hp_live_YOUR_API_KEY'}
)
data = response.json()
print(f'You have {len(data["pages"])} pages')DELETE/api/pages/{slug}
Delete a page permanently
Success Response (200 OK)
{
"success": true
}Examples
cURL
curl -X DELETE https://htmlpub.com/api/pages/my-page \
-H "Authorization: Bearer hp_live_YOUR_API_KEY"JavaScript (fetch)
const response = await fetch('https://htmlpub.com/api/pages/my-page', {
method: 'DELETE',
headers: {
'Authorization': 'Bearer hp_live_YOUR_API_KEY'
}
});
const result = await response.json();
console.log('Deleted:', result.success);Python (requests)
import requests
response = requests.delete(
'https://htmlpub.com/api/pages/my-page',
headers={'Authorization': 'Bearer hp_live_YOUR_API_KEY'}
)
result = response.json()
print('Deleted:', result['success'])GET/api/pages/{slug}/raw
Get the raw HTML content of a page (no authentication required)
Response
Returns the raw HTML content with Content-Type: text/html
Example
curl https://htmlpub.com/api/pages/my-page/rawAssets
Upload images, CSS, JavaScript, and fonts to your pages. Accepted types: png, jpg, gif, svg, webp, css, js, woff, woff2, ttf.
POST/api/pages/{slug}/assets
Upload a file to a page
Request Body (multipart/form-data)
Send a file using the file form field.
Success Response (201 Created)
{
"asset": {
"id": "asset_abc123",
"filename": "logo.png",
"contentType": "image/png",
"size": 45678
},
"url": "https://htmlpub.com/api/pages/my-page/assets/asset_abc123"
}Example
curl -X POST https://htmlpub.com/api/pages/my-page/assets \
-H "Authorization: Bearer hp_live_YOUR_API_KEY" \
-F "[email protected]"Plan Limits
- Free: 2MB per file, 10MB total, 10 assets per page
- Starter: 5MB per file, 100MB total, 50 assets per page
- Pro: 10MB per file, 500MB total, unlimited assets per page
GET/api/pages/{slug}/assets
List all assets for a page
Success Response (200 OK)
{
"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)
Returns the file with appropriate Content-Type. Cached with immutable headers (1 year).
DELETE/api/pages/{slug}/assets/{assetId}
Delete a specific asset
Success Response (200 OK)
{ "success": true }Sites
Group related pages into sites with their own URL namespace.
POST/api/sites
Create a new site
Request Body
{
"slug": "my-blog",
"name": "My Blog"
}Success Response (201 Created)
{
"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
Success Response (200 OK)
{
"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"
}
]
}PATCH/api/sites/{siteId}
Update a site's name, slug, or default page
Request Body (all fields optional)
{
"name": "Updated Name",
"slug": "new-slug",
"defaultPageId": "page-id-or-null"
}DELETE/api/sites/{siteId}
Delete a site and all its pages (cascading delete)
Cascading Delete
Deleting a site also permanently deletes all pages in that site.
Custom DomainsPro Plan
Connect your own domain to a page. Requires Pro plan.
POST/api/custom-domains
Add a custom domain
Request Body
{
"domain": "www.example.com",
"pageId": "cm1a2b3c4d5e6f7g8h9i0"
}Success Response (201 Created)
{
"domain": {
"domain": "www.example.com",
"status": "PENDING",
"pageId": "cm1a2b3c4d5e6f7g8h9i0"
},
"instructions": {
"target": "customers.htmlpub.com",
"message": "Add a CNAME record..."
}
}GET/api/custom-domains
List all custom domains
Success Response (200 OK)
{
"domains": [
{
"domain": "www.example.com",
"status": "ACTIVE",
"pageId": "cm1a2b3c4d5e6f7g8h9i0",
"verifiedAt": "2025-01-16T12:00:00.000Z",
"createdAt": "2025-01-15T10:30:00.000Z"
}
]
}PATCH/api/custom-domains/{domain}
Change which page a domain points to
Request Body
{ "pageId": "new-page-id" }DELETE/api/custom-domains/{domain}
Remove a custom domain
Success Response (200 OK)
{ "success": true }POST/api/custom-domains/{domain}/verify
Verify DNS configuration for a domain
Success Response (200 OK)
{
"domain": { ... },
"verified": true,
"cfStatus": "active"
}Rate Limits
Rate limits are applied per IP address in a 60-second rolling window.
| Endpoint | Limit |
|---|---|
| /api/pages (except /raw) | 20 requests per 60 seconds |
| /api/pages/.../raw | 60 requests per 60 seconds |
| /api/api-keys | 10 requests per 60 seconds |
Rate Limit Headers
All responses include rate limit information in headers:
X-RateLimit-Limit: 20
X-RateLimit-Remaining: 15When rate limited, you'll receive a 429 Too Many Requests response with a Retry-After: 60 header.
Error Codes
| Code | Description |
|---|---|
| 400 | Bad Request Invalid request body, missing required fields, or malformed data |
| 401 | Unauthorized Missing or invalid API key |
| 403 | Forbidden Insufficient permissions (e.g., page limit reached, Pro plan required) |
| 404 | Not Found Page does not exist or has expired |
| 405 | Method Not Allowed Wrong authentication method for this endpoint |
| 409 | Conflict Slug already exists for a different page |
| 413 | Payload Too Large HTML content exceeds 5MB limit |
| 429 | Too Many Requests Rate limit exceeded. Check Retry-After header |
| 500 | Internal Server Error Unexpected server error |
Error Response Format
{
"error": "Error message description"
}Plan Limits
API access requires the Pro plan. Here's what each plan includes:
| Feature | Free | Starter ($10/mo) | Pro ($29/mo) |
|---|---|---|---|
| Max Pages | 5 | 25 | 100 |
| Page Expiry | 7 days | Never | Never |
| Custom Slugs | ✗ | ✓ | ✓ |
| API Access | ✗ | ✗ | ✓ |
| Max API Keys | 0 | 0 | 5 |
| Custom Domains | ✗ | ✗ | ✓ |
| Analytics | ✗ | ✗ | ✓ |
| Max Asset Size | 2MB | 5MB | 10MB |
| Total Storage | 50MB | 100MB | 500MB |
| Assets per Page | 15 | 50 | Unlimited |