REST API 404 for Inactive Content

When enabled, the REST API returns 404 Not Found for anonymous GET requests whose target is an inactive content object (either expired or not yet effective). This prevents anonymous consumers from discovering scheduled or retired pages via the JSON API while still letting any authenticated user see everything.

Enabling the Feature

Set the environment variable WCS_REST_404_INACTIVE to a truthy value (1, true, yes, on) on the Plone process. The feature is disabled when the variable is unset or empty.

services:
  plone:
    environment:
      WCS_REST_404_INACTIVE: "true"

Behavior

A 404 response is returned when all of the following hold:

  • The request is served through the plone.restapi layer (JSON / /++api++/).

  • The HTTP method is GET.

  • The current user is anonymous.

  • The nearest content target of the request is inactive (expired or effective date in the future).

  • The target is determined as follows: if request.PUBLISHED is an IContentish object, that is the target; otherwise the first IContentish object found walking request.PARENTS is used.

The scope covers service endpoints nested under an inactive parent. For example, GET /++api++/<inactive-page>/@navigation returns 404 because the nearest content object in the parents chain is inactive.

What Is Not Affected

  • Site-root services such as GET /++api++/@search at the Plone site root — the parents chain ends at the site root, not at an inactive content object.

  • Classic (non-REST) requests — the gate is guarded by IPloneRestapiLayer, which is only provided on REST requests.

  • Authenticated users — any logged-in user bypasses the gate, regardless of role or permission.

  • Non-GET REST requests (POST, PATCH, DELETE) — editors must still be able to modify inactive content through the REST API.

Cloudflare Caveat

404 responses carry no Cache-Tag header, so flipping a live page to expired cannot be invalidated via tag-based purging. Cache invalidation for the transition from 200 to 404 requires URL-based purging of the affected paths on every subdomain.