PageDrop API Documentation
PageDrop lets you deploy HTML pages instantly via a simple REST API. Send your HTML in a POST request and receive a live URL in the response. Sites are hosted forever by default, or you can set a custom TTL. No signup, no API keys, no authentication required.
https://pagedrop.dev/api/v1
Authentication
No authentication is required. The API is completely free and open. Simply make HTTP requests to the endpoints below. Delete operations require the X-Delete-Token header, which is provided when a site is created.
POST /sites
Create a new hosted site. Accepts JSON with raw HTML, Markdown, or a multipart form upload with a file (HTML, PDF, or ZIP).
Option 1: JSON Body
| Field | Type | Required | Description |
|---|---|---|---|
html |
string | required* | Raw HTML content to host. Max 15 MB. *Provide html or markdown, not both. |
markdown |
string | required* | Markdown content — auto-rendered to a responsive, styled HTML page with dark/light mode support. Max 15 MB. *Provide html or markdown, not both. |
title |
string | optional | Page title for markdown rendering. Auto-detected from the first # heading if omitted. |
ttl |
string | optional | Time-to-live. Format: number + unit (h=hours, d=days, m=months). Examples: 1d, 12h, 30d. Default: never expires (permanent). |
slug |
string | optional | Custom vanity slug for the URL. 3-64 characters, lowercase alphanumeric and hyphens. Example: "my-project" → /s/my-project. Must be unique. |
ogTitle |
string | optional | Custom Open Graph title for social media previews. Max 200 characters. Injected as <meta property="og:title"> when the page is served. |
ogDescription |
string | optional | Custom Open Graph description for social media previews. Max 500 characters. Also sets <meta name="description">. |
ogImage |
string | optional | URL to an Open Graph image (1200x630px recommended). Enables twitter:card=summary_large_image for rich Twitter/X previews. |
password |
string | optional | Password-protect the page. Visitors must enter this password before viewing the content. |
passwordExpiry |
number | optional | Password cookie expiry in hours (1–720). Default: 24. After this period, visitors must re-enter the password. |
Option 2: Multipart Upload
| Field | Type | Required | Description |
|---|---|---|---|
file |
file | required | HTML file (.html), PDF file (.pdf), or ZIP archive (.zip) containing a project with an index.html at the root. PDF files are auto-wrapped in a viewer page. Max 10MB for PDF and ZIP. |
Example Request (JSON)
curl -X POST "https://pagedrop.dev/api/v1/sites" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Hello World</h1><p>Deployed with PageDrop!</p>"}'Example Request (Markdown)
curl -X POST "https://pagedrop.dev/api/v1/sites" \
-H "Content-Type: application/json" \
-d '{
"markdown": "# Hello World\n\nThis is **bold** and this is `code`.\n\n## Features\n\n- Auto-styled HTML\n- Dark/light mode\n- Responsive design",
"slug": "my-readme"
}'Example Request (JSON with OG Tags)
curl -X POST "https://pagedrop.dev/api/v1/sites" \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>My Demo</h1>",
"ogTitle": "My Demo Page",
"ogDescription": "Check out this awesome demo hosted on PageDrop",
"ogImage": "https://example.com/preview.png"
}'Example Request (PDF Upload)
curl -X POST "https://pagedrop.dev/api/v1/sites" \ -F "file=@document.pdf"
Example Request (ZIP Upload)
curl -X POST "https://pagedrop.dev/api/v1/sites" \ -F "file=@my-project.zip"
Success Response
{
"status": "success",
"data": {
"siteId": "a1b2c3d4",
"url": "https://pagedrop.dev/s/a1b2c3d4",
"deleteToken": "dlt_e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8",
"expiresAt": null,
"ttlDays": null,
"files": ["index.html"],
"totalSizeBytes": 1234,
"viewCount": 0,
"createdAt": "2026-02-17T12:00:00.000Z",
"passwordProtected": false
}
}deleteToken — it is only returned once at creation time. You need it to update or delete the site later.
PUT /sites/:siteId
Update an existing site's content without changing the URL. Requires the delete token from creation. Accepts the same content formats as POST.
Headers
| Header | Required | Description |
|---|---|---|
X-Delete-Token |
required | The delete token returned when the site was created. |
Example Request
curl -X PUT "https://pagedrop.dev/api/v1/sites/my-project" \
-H "Content-Type: application/json" \
-H "X-Delete-Token: dlt_your_token_here" \
-d '{"html": "<h1>Updated Content</h1>"}'Success Response
{
"status": "success",
"data": {
"siteId": "my-project",
"url": "https://pagedrop.dev/s/my-project",
"expiresAt": null,
"files": ["index.html"],
"totalSizeBytes": 512,
"updatedAt": "2026-02-27T12:00:00.000Z"
}
}GET /sites/:siteId
Retrieve metadata about a hosted site.
Path Parameters
| Param | Type | Description |
|---|---|---|
siteId |
string | The unique site identifier returned at creation |
Example Response
{
"status": "success",
"data": {
"siteId": "a1b2c3d4",
"url": "https://pagedrop.dev/s/a1b2c3d4",
"createdAt": "2026-02-17T12:00:00.000Z",
"expiresAt": null,
"fileCount": 1,
"totalSizeBytes": 1234,
"viewCount": 42,
"files": ["index.html"]
}
}DELETE /sites/:siteId
Delete a hosted site. Requires the X-Delete-Token header.
Headers
| Header | Required | Description |
|---|---|---|
X-Delete-Token |
required | The delete token returned when the site was created |
Example Request
curl -X DELETE "https://pagedrop.dev/api/v1/sites/a1b2c3d4" \ -H "X-Delete-Token: dlt_e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8"
Success Response
{
"status": "success",
"message": "Site a1b2c3d4 has been deleted"
}POST /sites/batch
Create multiple sites in a single request. Perfect for deploying AI-generated pages, student assignments, or multi-page projects. Supports HTML and Markdown content. Maximum 20 sites per request.
Request Body (JSON)
| Field | Type | Description |
|---|---|---|
sites |
Array | Array of site objects (max 20). Each object supports the same fields as POST /sites: html or markdown (required), plus optional title, slug, ttl, ogTitle, ogDescription, ogImage, password, passwordExpiry. |
Example Request
curl -X POST "https://pagedrop.dev/api/v1/sites/batch" \
-H "Content-Type: application/json" \
-d '{
"sites": [
{ "html": "<h1>Page One</h1>", "slug": "page-one", "ttl": "7d" },
{ "markdown": "# Page Two\nHello world", "title": "My Doc" },
{ "html": "<h1>Page Three</h1>" }
]
}'Success Response
{
"status": "success",
"data": {
"created": [
{
"siteId": "page-one",
"url": "https://pagedrop.dev/s/page-one",
"deleteToken": "dlt_abc123...",
"expiresAt": "2026-03-20T12:00:00.000Z",
"files": ["index.html"],
"totalSizeBytes": 22
},
{
"siteId": "a1b2c3d4",
"url": "https://pagedrop.dev/s/a1b2c3d4",
"deleteToken": "dlt_def456...",
"expiresAt": null,
"files": ["index.html"],
"totalSizeBytes": 150
},
{
"siteId": "e5f6g7h8",
"url": "https://pagedrop.dev/s/e5f6g7h8",
"deleteToken": "dlt_ghi789...",
"expiresAt": null,
"files": ["index.html"],
"totalSizeBytes": 24
}
],
"failed": [],
"totalCreated": 3,
"totalFailed": 0
}
}POST /sites/batch/delete
Delete multiple sites in a single request. Ideal for CI/CD cleanup pipelines and batch management. Maximum 50 sites per request.
Request Body (JSON)
| Field | Type | Description |
|---|---|---|
sites |
Array | Array of objects, each with siteId (string) and deleteToken (string) |
Example Request
curl -X POST "https://pagedrop.dev/api/v1/sites/batch/delete" \
-H "Content-Type: application/json" \
-d '{
"sites": [
{ "siteId": "abc123", "deleteToken": "dlt_token1..." },
{ "siteId": "def456", "deleteToken": "dlt_token2..." }
]
}'Success Response
{
"status": "success",
"data": {
"deleted": ["abc123"],
"failed": [
{ "siteId": "def456", "error": "Invalid delete token" }
],
"totalDeleted": 1,
"totalFailed": 1
}
}POST /sites/:siteId/fork
Fork (clone) a public site — creates an independent copy with new ownership. The original site is unchanged. The forked site gets its own delete token. Cannot fork password-protected sites.
Request Body (JSON, all optional)
| Field | Type | Description |
|---|---|---|
slug |
string | Custom vanity slug for the forked site |
ttl |
string | Time-to-live for the fork (e.g. "7d", "1h") |
ogTitle |
string | Override OG title (inherits from source if omitted) |
ogDescription |
string | Override OG description (inherits from source if omitted) |
ogImage |
string | Override OG image URL (inherits from source if omitted) |
Example Request
curl -X POST "https://pagedrop.dev/api/v1/sites/abc123/fork" \
-H "Content-Type: application/json" \
-d '{ "slug": "my-remix" }'Success Response
{
"status": "success",
"data": {
"siteId": "my-remix",
"url": "https://pagedrop.dev/s/my-remix",
"deleteToken": "dlt_...",
"expiresAt": null,
"files": ["index.html"],
"totalSizeBytes": 42,
"forkedFrom": "abc123",
"createdAt": "2026-03-17T..."
}
}GET /sites/:siteId/analytics
Get detailed view analytics for a site. Returns daily view counts, top referrers, and the 20 most recent individual views. Requires the delete token — only the site owner can access analytics.
Headers
| Header | Required | Description |
|---|---|---|
X-Delete-Token |
required | The delete token returned when the site was created |
Example Request
curl "https://pagedrop.dev/api/v1/sites/a1b2c3d4/analytics" \ -H "X-Delete-Token: dlt_e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8"
Success Response
{
"status": "success",
"data": {
"siteId": "a1b2c3d4",
"viewCount": 42,
"recentViews": [
{ "timestamp": "2026-03-11T14:30:00.000Z", "referrer": "https://twitter.com/someone/status/123" },
{ "timestamp": "2026-03-11T14:25:00.000Z", "referrer": null },
{ "timestamp": "2026-03-11T13:10:00.000Z", "referrer": "https://google.com" }
],
"dailyViews": {
"2026-03-11": 15,
"2026-03-10": 12,
"2026-03-09": 15
},
"topReferrers": [
{ "referrer": "(direct)", "count": 20 },
{ "referrer": "https://twitter.com/someone/status/123", "count": 12 },
{ "referrer": "https://google.com", "count": 10 }
]
}
}viewCount is the total lifetime count. Daily views and referrer breakdowns are computed from tracked recent views.
GET /sites/:siteId/files
List all files in a hosted site. Useful for ZIP-deployed sites with multiple assets.
Example Response
{
"status": "success",
"data": {
"siteId": "a1b2c3d4",
"files": [
{ "path": "index.html", "contentType": "text/html; charset=utf-8" },
{ "path": "style.css", "contentType": "text/css" },
{ "path": "app.js", "contentType": "application/javascript" }
],
"totalSizeBytes": 8192
}
}GET /s/:siteId
View the hosted site in a browser. This is the public URL users visit to see the deployed page. Returns the raw HTML content with appropriate Content-Type headers.
/api/v1 namespace. It serves the hosted content directly, not JSON metadata.
GET /embed/:siteId
Serve a hosted site in an iframe-friendly format. Uses Cross-Origin-Resource-Policy: cross-origin headers that allow embedding in blog posts, Notion pages, and other platforms. Password-protected sites return 403.
<iframe>. Example: <iframe src="https://pagedrop.dev/embed/my-page" width="100%" height="500"></iframe>
Response Format
All API endpoints return JSON. Successful responses are wrapped in a standard envelope with status: "success" and a data object. Error responses include error and message fields.
Error Response
{
"error": "Validation failed",
"message": "Request body must include 'html' field or upload a file"
}Error Codes
| Status | Meaning |
|---|---|
400 | Bad Request — Missing or invalid input (no HTML, invalid ZIP, exceeds size limit) |
401 | Unauthorized — Invalid or missing X-Delete-Token for DELETE operations |
403 | Forbidden — Cannot fork or embed a password-protected site |
404 | Not Found — Site does not exist or has expired |
413 | Payload Too Large — HTML exceeds 5 MB or ZIP exceeds 10 MB |
429 | Too Many Requests — Rate limit exceeded |
500 | Internal Server Error — Something went wrong on our end |
Rate Limits
The API applies rate limiting to protect service quality:
| Limit | Value |
|---|---|
| Site creations per minute | 10 per IP address |
| Read requests per minute | 60 per IP address |
| Window | 60 seconds sliding window |
When rate limited, the API returns a 429 status with a Retry-After header.
RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset.
Size Limits
| Resource | Limit |
|---|---|
| HTML body (JSON) | 5 MB |
| PDF upload | 10 MB |
| ZIP upload | 10 MB |
| Hosting duration | Forever (default), or set custom TTL up to 365 days via ttl parameter |
| Files per ZIP | 50 files max |
cURL Examples
# Deploy raw HTML
curl -X POST "https://pagedrop.dev/api/v1/sites" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Hello!</h1>"}'
# Upload a PDF file
curl -X POST "https://pagedrop.dev/api/v1/sites" \
-F "file=@document.pdf"
# Upload a ZIP project
curl -X POST "https://pagedrop.dev/api/v1/sites" \
-F "file=@my-project.zip"
# Get site metadata
curl "https://pagedrop.dev/api/v1/sites/a1b2c3d4"
# List files in a site
curl "https://pagedrop.dev/api/v1/sites/a1b2c3d4/files"
# Delete a site
curl -X DELETE "https://pagedrop.dev/api/v1/sites/a1b2c3d4" \
-H "X-Delete-Token: dlt_YOUR_TOKEN_HERE"JavaScript Example
// Deploy HTML
const response = await fetch("https://pagedrop.dev/api/v1/sites", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
html: "<h1>Hello World</h1><p>Deployed with PageDrop</p>"
})
});
const { data } = await response.json();
console.log(`Live at: ${data.url}`);
console.log(`Expires: ${data.expiresAt ?? "Never"}`);
console.log(`Delete token: ${data.deleteToken}`); // Save this!
// Get site metadata
const meta = await fetch(`https://pagedrop.dev/api/v1/sites/${data.siteId}`);
console.log(await meta.json());
// Upload a PDF file
const pdfForm = new FormData();
pdfForm.append("file", pdfBlob, "document.pdf");
const pdfUpload = await fetch("https://pagedrop.dev/api/v1/sites", {
method: "POST",
body: pdfForm
});
console.log(await pdfUpload.json());
// Upload a ZIP file
const formData = new FormData();
formData.append("file", zipFileBlob, "project.zip");
const upload = await fetch("https://pagedrop.dev/api/v1/sites", {
method: "POST",
body: formData
});
console.log(await upload.json());
// Delete a site
await fetch(`https://pagedrop.dev/api/v1/sites/${data.siteId}`, {
method: "DELETE",
headers: { "X-Delete-Token": data.deleteToken }
});Python Example
import requests
# Deploy HTML
response = requests.post(
"https://pagedrop.dev/api/v1/sites",
json={"html": "<h1>Hello World</h1><p>Deployed with PageDrop</p>"}
)
result = response.json()
site = result["data"]
print(f"Live at: {site['url']}")
print(f"Expires: {site['expiresAt'] or 'Never'}")
print(f"Delete token: {site['deleteToken']}") # Save this!
# Get site metadata
meta = requests.get(f"https://pagedrop.dev/api/v1/sites/{site['siteId']}")
print(meta.json())
# Upload a PDF file
with open("document.pdf", "rb") as f:
pdf_upload = requests.post(
"https://pagedrop.dev/api/v1/sites",
files={"file": ("document.pdf", f, "application/pdf")}
)
print(pdf_upload.json())
# Upload a ZIP file
with open("my-project.zip", "rb") as f:
upload = requests.post(
"https://pagedrop.dev/api/v1/sites",
files={"file": ("project.zip", f, "application/zip")}
)
print(upload.json())
# Delete a site
requests.delete(
f"https://pagedrop.dev/api/v1/sites/{site['siteId']}",
headers={"X-Delete-Token": site["deleteToken"]}
)MCP Integration
PageDrop supports the Model Context Protocol (MCP), allowing AI assistants like Claude, VS Code Copilot, Cursor, and other MCP-compatible clients to deploy and manage HTML sites directly.
Installation
Add the following to your claude_desktop_config.json (or equivalent MCP client configuration):
{
"mcpServers": {
"pagedrop": {
"command": "npx",
"args": ["-y", "pagedrop-mcp"]
}
}
}Available Tools
| Tool | Description | Parameters |
|---|---|---|
deploy_html |
Deploy HTML or Markdown content and get a live URL instantly | html or markdown (one required), title, ttl, slug, ogTitle, ogDescription, ogImage, password, passwordExpiry (all optional) |
get_site_info |
Retrieve metadata about a hosted site | siteId (required) |
delete_site |
Delete a hosted site using its delete token | siteId (required), deleteToken (required) |
batch_delete |
Delete multiple sites at once (max 50) | sites (required) — array of {siteId, deleteToken} |
Environment Variables
| Variable | Default | Description |
|---|---|---|
PAGEDROP_BASE_URL |
https://pagedrop.dev |
Override the base URL for API requests (useful for self-hosted instances) |