---
title: sherpage
description: The v1 reference for publishing and managing pages.
---

# sherpage

Base path `/api/sherpage/v1`. See [API reference](/api) for
authentication and the `api.shebang.pro` rewrite.

Every route requires an `shb_...` key or bearer with `page` in scope
(master keys always qualify). `project` — a project slug, query
parameter on `GET`/list, body field on `POST` — behaves exactly as
described in [platform](/api/platform#projects): omitted defaults to the
key's home project, a write against a named project needs the home
project or a `full` grant, and an unreachable project is `403
insufficient_scope`, never `404`.

A page's access level, password, expiry, and view budget live on its
[sherlink](/api/sherlink) short link, not on the page row — see
[access modes](/concepts/access-modes).

The **Page object** every route below returns:

```json
{
  "id": "uuid",
  "public_id": "string",
  "url": "string",
  "shortUrl": "string | null",
  "slug": "string",
  "title": "string",
  "entrypoint": "string",
  "access": "private | password | allow_list | public",
  "status": "active | archived",
  "expiresAt": "ISO 8601 | null",
  "maxViews": "number | null",
  "viewCount": "number",
  "sizeBytes": "number",
  "fileCount": "number",
  "createdAt": "ISO 8601",
  "updatedAt": "ISO 8601",
  "archivedAt": "ISO 8601 | null"
}
```

## Pages

### GET /api/sherpage/v1/pages

Lists pages.

- **Auth** — `page` scope.
- **Query params** — `status` (`"active"` or `"archived"`, defaults to
  `"active"`), `project` (slug, optional).
- **Response** — `200 { "pages": [Page, ...], "project": "slug" }`.
- **Errors** — `400 invalid status filter`, `403 insufficient_scope`.

### POST /api/sherpage/v1/pages

Publishes a new page — creates the resource row, uploads the bundle, and
mints its short link in one call. Rolls back everything already created
if a later step fails.

- **Auth** — `page` scope, write access to the target project.
- **Request body** — `title` (string, 1–200 chars, required), `files`
  (non-empty array of `{ path, content, encoding: "utf8" | "base64" }`,
  required), `slug` (string, optional — derived from `title` if
  omitted), `entrypoint` (string, optional, default `"index.html"`),
  `access` (default `"private"`), `password` (string, required if
  `access` is `"password"`), `expiresAt` (ISO 8601, optional),
  `maxViews` (positive integer, optional), `alias` (string, optional),
  `allow` (array of emails/handles, optional — grants for
  `access: "allow_list"`), `project` (slug, optional).
- **Response** — `201 { "id", "public_id", "url", "shortUrl", "slug",
  "access", "fileCount", "sizeBytes", "project" }`.
- **Errors** — `400` (missing/invalid `title`, `entrypoint`, `access`,
  `password`, `expiresAt`, `maxViews`, `alias`, an unresolvable `allow`
  entry, an unsafe file path, a malformed file entry, or an undecodable
  base64 body), `400 password_required` (`access: "password"` with no
  `password`), `409 slug_conflict`, `409 alias_conflict`, `413` (too many
  files, or a file/bundle over the size limit), `403 insufficient_scope`,
  `500 public_id_exhausted`.

### GET /api/sherpage/v1/pages/{id}

Fetches one page.

- **Auth** — `page` scope, read access to the page's project.
- **Path params** — `id` (uuid).
- **Response** — `200 Page`.
- **Errors** — `404 not_found`, `403 insufficient_scope`.

### PATCH /api/sherpage/v1/pages/{id}

Updates a page's content fields and/or its link's policy fields. The
content row commits first; if the link update then fails, the row is
reverted — a failed `PATCH` never leaves the two half-applied.

- **Auth** — `page` scope, write access to the page's project.
- **Path params** — `id` (uuid).
- **Request body** — any of `title`, `slug`, `access`, `password`
  (string or `null`), `expiresAt` (string or `null`), `maxViews` (number
  or `null`), `alias` (string or `null`), `status` (`"active"` or
  `"archived"`) — all optional.
- **Response** — `200 Page`.
- **Errors** — `400` (invalid field value), `404 not_found`, `409
  slug_taken`, `409 alias_conflict`, `400 password_required`, `403
  insufficient_scope`.

### DELETE /api/sherpage/v1/pages/{id}

Deletes a page — its storage bundle, its short link, then the row.

- **Auth** — `page` scope, write access to the page's project.
- **Path params** — `id` (uuid).
- **Response** — `204` (empty body).
- **Errors** — `404 not_found`, `403 insufficient_scope`, `500
  storage_cleanup_failed`, `500 link_cleanup_failed`.

## Files

### GET /api/sherpage/v1/pages/{id}/files

Lists a page's uploaded files.

- **Auth** — `page` scope, read access to the page's project.
- **Path params** — `id` (uuid).
- **Response** — `200 { "files": [...] }`.
- **Errors** — `404 not_found`, `403 insufficient_scope`.

### POST /api/sherpage/v1/pages/{id}/files

Adds files to an existing page's bundle. Validates the whole batch before
uploading anything — a rejected call leaves existing files untouched.

- **Auth** — `page` scope, write access to the page's project.
- **Path params** — `id` (uuid).
- **Request body** — `files` (non-empty array, same shape as `POST
  /pages`'s `files`, required).
- **Response** — `200 { "id", "fileCount", "sizeBytes" }`.
- **Errors** — `400` (malformed file entry, unsafe path, undecodable
  base64), `404 not_found`, `403 insufficient_scope`, `413` (too many
  files, or over the size limit).

## Shared

### GET /api/sherpage/v1/shared

Lists pages someone else has shared with the caller's account (matched by
user id or email against `allow_list` grants).

- **Auth** — `page` scope.
- **Response** — `200 { "pages": [{ "pageId", "title", "slug",
  "public_id", "ownerHandle", "url", "sharedAt", "expiresAt", "maxViews"
  }, ...] }`.

## Next

[sherserve](/api/sherserve) — uploaded files.
