Pages
The pages API lets you create, update, list, and delete pages. The POST /pages endpoint is the primary entry point. It accepts one or many pages, optional inline media files, optional deletions, and an optional build trigger, all in one request.
POST /pages
Create or update pages. Supports both JSON and multipart requests.
JSON request
Send Content-Type: application/json with the payload as the body.
curl -X POST https://sitepaste.com/api/v1/public/pages \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"pages": [
{
"title": "Getting started",
"slug": "getting-started",
"content": "# Getting started\n\nWelcome to the docs.",
"contentType": "docs",
"tags": ["tutorial"]
}
],
"build": true
}'
Multipart request
Send Content-Type: multipart/form-data with a payload field containing the JSON and file parts for inline media. See the media docs for details on inline file uploads.
curl -X POST https://sitepaste.com/api/v1/public/pages \
-H "Authorization: Bearer $TOKEN" \
-F payload='{"pages": [{"title": "Beach day", "content": ""}]}' \
-F files=@sunset.jpg
Payload fields
| Field | Type | Description |
|---|---|---|
| siteId | string | Target site UUID. Falls back to your default site if omitted. |
| pages | array | One or more page objects (max 5000). |
| deleteSlugs | array | Page entries to delete in the same transaction (max 5000). Each entry is an object with slug (required), contentType (required), and section (optional). |
| build | boolean | When true, triggers a site build after all page operations complete. |
Page fields
Every field except the identifier is optional on updates. Omitted fields preserve their existing values.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | No | Page UUID. When provided, the page is looked up by ID and all fields including slug can be updated. |
| slug | string | No | URL slug (max 100 chars). When id is not provided, this is the upsert key. Auto-generated from title if both id and slug are omitted. |
| title | string | Conditional | Page title (max 200 chars). Required when creating a new page. Optional on updates. |
| content | string | No | Markdown content (max 100,000 chars). May contain file: references to inline media. |
| contentType | string | No | One of blog, docs, homepage, standalone. Defaults to blog for new pages. |
| section | string | No | Optional section slug (max 100 chars). Groups pages within a content type, producing URLs like /docs/my-section/my-page. Only lowercase letters, numbers, and hyphens are allowed. No consecutive hyphens. When omitted on updates, the existing value is preserved. |
| description | string | No | Meta description (max 500 chars). |
| draft | boolean | No | When true, the page is hidden from the published site. Defaults to false for new pages. |
| tags | array | No | Array of strings (max 20 tags, each max 30 chars). When present, replaces all existing tags. When omitted, existing tags are preserved. An empty array [] removes all tags. |
| publishedAt | string | No | ISO 8601 timestamp (e.g. 2026-02-08T12:00:00Z). Defaults to now for new pages. Preserved on updates if omitted. |
| params | object | No | Arbitrary JSON stored as Hugo front matter params. When present, replaces existing params. When omitted, existing params are preserved. |
Page identification
There are three ways to identify a page in the batch:
By id: provide id as a UUID. The page is looked up by primary key. You can update any field including slug. If the ID does not match a page in the target site, the request fails.
{"id": "a1b2c3d4-...", "slug": "new-slug", "title": "Renamed page"}
By slug: provide slug without id. If a page with that slug exists, it is updated. If not, a new page is created (and title is required).
{"slug": "my-page", "draft": true}
Auto: omit both id and slug. A slug is generated from title, which is required. If a page with the generated slug already exists, it is updated.
{"title": "My new post", "content": "Hello world"}
Partial updates
You only need to send the fields you want to change. Everything else is preserved.
{"pages": [{"slug": "my-page", "draft": true}]}
This flips the draft status without touching the title, content, tags, or any other field.
More examples:
{"pages": [{"slug": "my-page", "title": "Better title"}]}
{"pages": [{"slug": "my-page", "tags": ["updated"]}]}
{"pages": [{"id": "a1b2c3d4-...", "slug": "renamed-slug"}]}
Deletions
Use deleteSlugs to soft-delete pages in the same transaction as upserts. Each entry requires a slug and contentType. If the page has a section, include section as well. Entries that do not match an existing page are silently ignored.
{
"pages": [{"title": "New post", "content": "Hello"}],
"deleteSlugs": [
{"slug": "old-post-1", "contentType": "blog"},
{"slug": "old-post-2", "contentType": "docs", "section": "guides"}
],
"build": true
}
A slug cannot appear in both pages and deleteSlugs.
Build trigger
When build is true, a site build is queued after all page operations commit. The build is subject to your plan’s monthly quota and a 30-second cooldown between builds. If the build cannot be triggered (quota exceeded, cooldown active), the pages are still saved and the response includes the build error.
Response: 200 OK
{
"pages": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"slug": "getting-started",
"title": "Getting started",
"contentType": "docs",
"section": "guides",
"draft": false,
"status": "created",
"createdAt": "2026-02-08T12:00:00Z",
"updatedAt": "2026-02-08T12:00:00Z"
}
],
"deleted": ["old-post-1"],
"media": [
{"filename": "sunset.jpg", "url": "/media/.../a1b2c3d4.webp"}
],
"build": {
"status": "queued",
"deployUrl": "https://yourusername.sitepaste.com"
}
}
The status field on each page is created for new pages or updated for modified pages. The section field is only present when the page has a section. The deleted, media, and build fields are only present when applicable.
Legacy format
The endpoint also accepts the original single-page format for backward compatibility:
{
"title": "My post",
"content": "Hello world",
"slug": "my-post"
}
This is equivalent to wrapping it in {"pages": [...]}.
Error responses
Validation errors include the page index and field name:
{
"error": "validation failed",
"pages": {
"0": {"title": "title is required for new pages"},
"2": {"contentType": "invalid contentType; must be one of: blog, docs, homepage, standalone"}
}
}
Duplicate slugs within a batch (scoped by content type and section):
{
"error": "duplicate slugs in batch",
"conflicts": [[0, 3]]
}
Slug appears in both pages and deleteSlugs:
{
"error": "slug conflict",
"message": "one or more slugs appear in both pages and deleteSlugs",
"pageIndexes": [2]
}
ID not found:
{
"error": "validation failed",
"pages": {"0": {"id": "page not found for the provided id"}}
}
Storage quota exceeded: 402 Payment required
{
"error": "storage quota exceeded",
"message": "Upload requires 15 MB but only 3 MB of storage remains"
}
GET /pages
List all pages for a site. Returns metadata only; content bodies are not included.
Request
curl https://sitepaste.com/api/v1/public/pages \
-H "Authorization: Bearer $TOKEN"
Query parameters:
| Parameter | Description |
|---|---|
| siteId | Target site UUID. Falls back to your default site if omitted. |
| contentType | Filter by content type (blog, docs, homepage, standalone). |
Response: 200 OK
{
"pages": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"slug": "getting-started",
"title": "Getting started",
"contentType": "docs",
"section": "guides",
"description": "How to get started",
"draft": false,
"tags": ["tutorial"],
"publishedAt": "2026-01-15T10:00:00Z",
"updatedAt": "2026-02-08T12:00:00Z"
}
]
}
The id field can be used for ID-based updates in subsequent POST /pages calls. The section field is only present when the page has a section.
DELETE /pages/{slug}
Delete a single page by slug.
Request
curl -X DELETE "https://sitepaste.com/api/v1/public/pages/old-post?contentType=blog" \
-H "Authorization: Bearer $TOKEN"
Query parameters:
| Parameter | Required | Description |
|---|---|---|
| contentType | Yes | The content type of the page to delete (blog, docs, homepage, standalone). |
| siteId | No | Target site UUID. Falls back to your default site if omitted. |
| section | No | Section slug. Required when deleting a page that belongs to a section. |
Response: 204 No content
Returns 404 if no page with that slug, content type, and section exists in the site.