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": "![sunset](file:sunset.jpg)"}]}' \
  -F files=@sunset.jpg

Payload fields

FieldTypeDescription
siteIdstringTarget site UUID. Falls back to your default site if omitted.
pagesarrayOne or more page objects (max 5000).
deleteSlugsarrayPage entries to delete in the same transaction (max 5000). Each entry is an object with slug (required), contentType (required), and section (optional).
buildbooleanWhen 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.

FieldTypeRequiredDescription
idstringNoPage UUID. When provided, the page is looked up by ID and all fields including slug can be updated.
slugstringNoURL 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.
titlestringConditionalPage title (max 200 chars). Required when creating a new page. Optional on updates.
contentstringNoMarkdown content (max 100,000 chars). May contain file: references to inline media.
contentTypestringNoOne of blog, docs, homepage, standalone. Defaults to blog for new pages.
sectionstringNoOptional 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.
descriptionstringNoMeta description (max 500 chars).
draftbooleanNoWhen true, the page is hidden from the published site. Defaults to false for new pages.
tagsarrayNoArray 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.
publishedAtstringNoISO 8601 timestamp (e.g. 2026-02-08T12:00:00Z). Defaults to now for new pages. Preserved on updates if omitted.
paramsobjectNoArbitrary 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:

ParameterDescription
siteIdTarget site UUID. Falls back to your default site if omitted.
contentTypeFilter 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:

ParameterRequiredDescription
contentTypeYesThe content type of the page to delete (blog, docs, homepage, standalone).
siteIdNoTarget site UUID. Falls back to your default site if omitted.
sectionNoSection 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.