Skip to content
  1. Extras
  2. PageBuilder
  3. PageBuilder Pro
  4. REST API v1

REST API v1 (Pro) ​

Read-only published content. You need PageBuilder Pro and pagebuilder_rest_api_enabled = 1. It does not replace the Free Public API. Page writes stay in the manager and in Agent API.

text
/assets/components/pagebuilder/api/v1.php?path=/pages/42

Which API ​

  1. One frontend and a key in server env: Public API.
  2. Several apps, token revoke, scopes, and If-None-Match: REST v1.
  3. Draft or field schemas from the manager: Agent API, not this transport.
Public API (Free)REST API v1 (Pro)
URLapi.phpapi/v1.php?path=…
AuthX-PageBuilder-Api-Key or query api_keyAuthorization: Bearer
Page listnoGET /pages
One pageaction=web/page/getGET /pages/{id-or-alias}
Type catalogaction=web/catalog/listGET /catalog/sections
ETag / 304noyes
Per-token throttlenoyes

After publish in the manager, publishedRevision grows. PageBuilder does not send an outbound webhook. The frontend compares the revision itself.

Settings ​

Keys: system settings. Issue tokens on the CMP API tokens tab.

KeyDefaultDescription
pagebuilder_rest_api_enabled0Enable the transport
pagebuilder_rest_token_pepperemptySHA-256 pepper. Empty means site_id. If site_id is empty too, the pepper is pagebuilder
pagebuilder_rest_tokens[]Token JSON without secrets
pagebuilder_rest_throttle_per_minute120Request limit per token prefix. 0 disables the limit

Authorization ​

http
Authorization: Bearer <secret>

The secret is shown once when you issue the token in the CMP. The setting keeps prefix and hash. A revoked or expired token returns 401. Over the throttle the response is 429 with Retry-After. Bucket: {token.prefix}|read or |write. The IP is not part of the bucket.

Scope pages.read opens GET /pages and GET /pages/{id-or-alias}. This token cannot write.

Routes ​

ScopeRoute
catalog.readGET /health, GET /catalog/sections
pages.readGET /pages, GET /pages/{id-or-alias}

GET /pages returns pages with publishedRevision > 0. The list has no document body.

The selection includes every such page in the context when the resource is published, not deleted, and allowed by the view policy. Sort is menuindex, then resource id. offset and limit slice that list. total counts pages before the slice. A q search narrows both the list and total.

QueryDescription
limit1–100, default 20
offsetOffset
contextContext, default web
qSearch pagetitle, alias, or id

GET /pages/{id-or-alias} returns one page by numeric id or alias.

QueryDescription
includedocument, values, html, sections. Same as web/page/get. Default document,values
contextContext for alias
section_typesType filter, for example hero,cta

The envelope is { success, message, object }, same as Public API.

OpenAPI: GET /openapi.json. Paths are filtered by the token scopes. Without Bearer the schema returns both read scopes.

Cache and ETag ​

With Bearer the response uses Cache-Control: private, no-store and an ETag header. A matching If-None-Match returns 304 and an empty object.

Also: X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, CSP default-src 'none'; frame-ancestors 'none'.

curl ​

bash
BASE="https://cms.example.com/assets/components/pagebuilder/api/v1.php"
TOKEN="pb_live_secret"

curl -s -H "Authorization: Bearer $TOKEN" \
  "${BASE}?path=/health"

curl -s -H "Authorization: Bearer $TOKEN" \
  "${BASE}?path=/pages&limit=20&q=about"

curl -s -H "Authorization: Bearer $TOKEN" \
  "${BASE}?path=/pages/42&include=document,values"

curl -s -H "Authorization: Bearer $TOKEN" \
  -H 'If-None-Match: "abc"' \
  "${BASE}?path=/pages/42&include=document,values" -D -

Next.js (App Router) ​

Keep PAGEBUILDER_REST_URL and PAGEBUILDER_REST_TOKEN on the server, not in NEXT_PUBLIC_*.

typescript
export async function restGetPage(idOrAlias: string | number, etag?: string) {
  const url = new URL(process.env.PAGEBUILDER_REST_URL!)
  url.searchParams.set('path', `/pages/${idOrAlias}`)
  url.searchParams.set('include', 'document,values')

  const headers: HeadersInit = {
    Authorization: `Bearer ${process.env.PAGEBUILDER_REST_TOKEN!}`,
  }
  if (etag) headers['If-None-Match'] = etag

  const res = await fetch(url, { headers, cache: 'no-store' })
  if (res.status === 304) {
    return { status: 304 as const, page: null, etag: res.headers.get('ETag') }
  }
  const body = await res.json()
  return {
    status: res.status,
    page: body.success ? body.object : null,
    etag: res.headers.get('ETag'),
  }
}

List for a sitemap:

typescript
export async function restListPages(limit = 50) {
  const url = new URL(process.env.PAGEBUILDER_REST_URL!)
  url.searchParams.set('path', '/pages')
  url.searchParams.set('limit', String(limit))
  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.PAGEBUILDER_REST_TOKEN!}` },
    next: { revalidate: 60 },
  })
  const body = await res.json()
  if (!body.success) throw new Error(body.message)
  return body.object as { items: unknown[]; total: number }
}

Nuxt 4 ​

Put the token only in runtimeConfig, not in public. BFF:

typescript
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'id')!
  const config = useRuntimeConfig()
  const url = new URL(config.pageBuilderRestUrl)
  url.searchParams.set('path', `/pages/${id}`)
  url.searchParams.set('include', 'document,values')

  const data = await $fetch(url.toString(), {
    headers: { Authorization: `Bearer ${config.pageBuilderRestToken}` },
  })
  if (!data.success) {
    throw createError({ statusCode: 404, statusMessage: data.message })
  }
  return data.object
})
vue
<script setup lang="ts">
const route = useRoute()
const { data: page } = await useAsyncData(
  () => `rest-${route.params.id}`,
  () => $fetch(`/api/cms/${route.params.id}`),
)
</script>