Protocol

← All sections · part of the machine-readable /all/ index.

Protocol

Protocol

Reference for wire-level HTTP details — capability discovery via OPTIONS and file-level authoring via WebDAV.

Pages in this group

See also

OPTIONS

OPTIONS method

The HTTP OPTIONS method discovers which methods are allowed on a resource or a specific element within a resource. It is the capability-discovery mechanism that pagelove/primitives.mjs uses to determine available operations.

When to reach for it

Use OPTIONS before performing writes to confirm that the authenticated user is authorized for the intended method and selector. OPTIONS does not modify the document.

Allowed methods are governed by authorization rules. A rule granting the wildcard method (*) is reported as the concrete methods the server supports on the target — GET, HEAD, PUT, DELETE, POST, MOVE, PATCH for a whole document, or that set without MOVE and PATCH for a selector-scoped grant — so the Allow header never contains a literal *.

Standard response

An OPTIONS request without a selector range returns the allowed methods for the resource as a whole. The response includes Accept-Ranges: selector when the resource is HTML.

Selector-scoped OPTIONS

When an OPTIONS request includes a selector range unit, the server returns the methods allowed for that specific element:

OPTIONS /options-test.html
Range: selector=h1
HTTP/1.1 200
Allow: *, OPTIONS
Accept-Ranges: selector

Multipart OPTIONS

A client can discover capabilities for every addressable element in a single request. Send an OPTIONS request with Accept: multipart/mixed; that header selects the multipart form.

The server responds with 207 Multi-Status when the target path has at least one selector-scoped authorization rule (a rule that names an element, not just the whole document). The body is a multipart message where each part carries an Allow line and a Content-Range line identifying the element:

OPTIONS /options-test.html
Accept: multipart/mixed
HTTP/1.1 207
Content-Type: multipart/mixed; boundary=boundaryb7bbf5b36e83dd18

--boundaryb7bbf5b36e83dd18
Allow: GET, OPTIONS

--boundaryb7bbf5b36e83dd18
Content-Range: selector=h1
Allow: GET, POST, PUT, OPTIONS

--boundaryb7bbf5b36e83dd18--

When there are no selector-scoped rules

Accept: multipart/mixed requests the multipart form, but the server can only produce it when there is per-element authorization to report. If the path has only document-level rules — or no matching rules at all — there is nothing element-specific to enumerate, so the server falls back to a plain 204 No Content with a single aggregated Allow header covering the whole document (the same headers as the standard response — Accept-Ranges: selector, Vary: Authorization, Accept — just with a 204 status and no body).

So a client that always sends Accept: multipart/mixed for capability discovery must handle both responses: a 207 multipart body (per-element) and a bodyless 204 with a flat Allow header (document-level only). Read the status code first — don't assume a multipart body is present.

OPTIONS never checks whether the target exists

OPTIONS answers "what could I do here if I had permission?" purely from authorization rules matched against the request path and selector — it never looks up the document or evaluates the selector against real content. A request for a document that doesn't exist, or a selector that would match no element, still gets a normal response listing whatever methods the rules grant for that path, never a not-found or range error. To find out whether a document or element actually exists, issue the write (or a GET) itself.

See also

WebDAV

WebDAV

The WebDAV authoring interface exposes each Pagelove site as a standard WebDAV mount. Any filesystem client that speaks WebDAV can browse and edit content directly.

When to reach for it

Use WebDAV for file-level authoring — uploading assets, reorganizing directories, or editing documents with desktop tools. Each site has its own mount URL. Credentials are configured in the Pagelove control panel.

Live site

The WebDAV mount represents the live site. Pagelove modifies the DOM in place, so another user or an API call may update a document between the time it is read and the time it is written back. The author is responsible for coordinating concurrent changes.

Directory listings and revalidation

Every directory has a version tag (an ETag). A client that already has a copy of a listing can send that tag back on its next PROPFIND as If-None-Match; if nothing in the directory has changed, the server answers 304 Not Modified with an empty body and the client reuses what it has.

The tag belongs to the question you asked, not just to the directory. Depth: 0 asks for the directory's own properties; Depth: 1 (the default when no Depth header is sent) asks for the directory and everything directly inside it. Those are two different answers, so they carry two different tags. Sending a Depth: 0 tag with a Depth: 1 request returns the full listing rather than a 304 — a listing is never answered with "nothing changed" on the strength of a stat.

The tag appears both as the ETag response header and as the <D:getetag> property of the directory's own entry in the multistatus body; the two always agree. Responses also carry Vary: Depth, so a caching proxy placed in front of the authoring server keeps the two depths apart.

A directory's tag changes whenever a child is added, removed, or edited. PROPFIND on a single file is currently always answered in full — If-None-Match is not yet honoured there, so don't rely on never receiving a 304 for a file PROPFIND.

When something goes wrong

An error response carries an HTML document describing what happened, not just a status code. It is marked up so a program can read it:

<article itemscope itemtype="https://pagelove.org/Error">
  <meta itemprop="status" content="422">
  <meta itemprop="kind" content="UntranslatableWrite">
  <div itemprop="type" itemscope
       itemtype="https://dombase.pagelove.team/ns/error/UntranslatableWrite"></div>
  <p itemprop="message">…</p>
</article>

The kind names the specific failure, and the nested item repeats it as a type you can match on. Those names are the same ones the HTTP interface uses, so a client that already understands errors from one interface understands them from the other — the server never invents a name of its own here.

When a write is refused by the shared pipeline, the pipeline's own error document is included unchanged under a detail property, so you get its full description rather than a summary of it.

The status code tells you what to do about it, and it is worth distinguishing two that look similar:

Other codes you may see: 409 when a write conflicts with a concurrent change, 400 for a path the server will not accept, 422 when the content does not satisfy the site's schema or constraints, 503 when the storage layer is briefly unavailable and the request can be retried, and 507 when the request exhausted its allowance.

See also

Accept-Ranges

Accept-Ranges

The Accept-Ranges response header advertises which Range units a resource supports.

Standard response

Accept-Ranges: selector, bytes

Present on every response for an HTML resource, whether or not the request itself used a Range header. OPTIONS responses advertise just Accept-Ranges: selector (see OPTIONS method).

entries and sessel ranging both work on Range requests — the platform accepts and answers them correctly — but neither is advertised in Accept-Ranges. If your client hard-codes Range: selector=..., Range: entries=..., or Range: sessel=... rather than deriving support from Accept-Ranges, this doesn't affect you. See Reading and writing for what each unit does.

Known issue: some CDNs narrow this header further

Known issue: Pagelove is currently served through Microsoft Azure Front Door, which rewrites Accept-Ranges on the way through — replacing selector, bytes with just bytes, the only unit AFD itself understands. Range: selector=... requests still work end-to-end and the response you get back is correct; only header-based discovery is affected, in the same way and for the same reason as the entries/sessel gap above. There is currently no workaround at the CDN layer.

See also